What is CO?

Co is a library for automatic execution of Generator functions, which can be understood as a syntactic sugar. It is a gadget released by programmer TJ Holowaychuk in June 2013. Source code address

Generator functions are an asynchronous programming solution provided by ES6 that can be used to control the execution of functions. The state of the function is controlled by the yield keyword, and next() is executed.

The CO function is similar to async await. It is a Generator sugar and does not require us to yield/next.

The main function

The co function takes a Generator and returns a Promise

function co(gen) {
  // Get the context this
  var ctx = this;
  // Get all the arguments passed to the function
  var args = slice.call(arguments.1);
	/ / return promise
  return new Promise(function(resolve, reject) {
    // If gen is a function, execute generator first
    if (typeof gen === 'function') gen = gen.apply(ctx, args);
    // If gen has a value or gen non-iterator object resolve directly
    if(! gen ||typeofgen.next ! = ='function') return resolve(gen);
	  Next: function() {},done: true or false}' */ 
    
    // The first yield is used, since gen. Next () is not passed
    onFulfilled();
		
    
    function onFulfilled(res) {
      var ret;
      try {
        // Call gen.next to yield the res argument
        ret = gen.next(res);
      } catch (e) {
        // Error returns directly
        return reject(e);
      }
      next(ret); // Accept gen.next to return the result to go to the judgment logic
      return null;
    }
		// gen executes throw, reject
    function onRejected(err) {
      var ret;
      try {
        ret = gen.throw(err);
      } catch (e) {
        return reject(e); 
      }
      next(ret);
    }
		 // Return is just to end the function
    function next(ret) {
      // If generator completes, resolve is returned
      if (ret.done) return resolve(ret.value);
      // Done is false. Convert ret. Value to promise
      var value = toPromise.call(ctx, ret.value);
      // If this is a promise, then continue the chain onFulfilled, onRejected operation
      // This is the next recursion
      if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
      // Throw an error if the result of the conversion is not a promise
      return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
        + 'but the following object was passed: "' + String(ret.value) + '"')); }}); }Copy the code

Auxiliary function

toPromise

function toPromise(obj) {
  // False values are returned directly
  if(! obj)return obj;
  // Returns directly for promise
  if (isPromise(obj)) return obj;
  // is a generator generator function or generator iterator object that calls co
  if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
  // Returns a promise for the function
  if ('function'= =typeof obj) return thunkToPromise.call(this, obj);
  // Convert the promise to an array
  if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
  // Convert the promise to the object
  if (isObject(obj)) return objectToPromise.call(this, obj);
  / / return
  return obj;
}
Copy the code

thunkPromise

Use thunk to convert a function to a promise. Thunk function meaning and usage

function thunkToPromise(fn) {
  var ctx = this;
  return new Promise(function (resolve, reject) {
    fn.call(ctx, function (err, res) {
      if (err) return reject(err);
      if (arguments.length > 2) res = slice.call(arguments.1);
      resolve(res);
    });
  });
}
Copy the code

arrayToPromise

Convert each item of the array to a promise. Call Promise.all returns.

function arrayToPromise(obj) {
  return Promise.all(obj.map(toPromise, this));
}
Copy the code

objectToPromise

Convert each item of the object to a promise. Call Promise.all returns.

function objectToPromise(obj){
  // Initialize result
  var results = new obj.constructor();
  // Facilitate all key names
  var keys = Object.keys(obj);
  // An array of promises
  var promises = [];
  for (var i = 0; i < keys.length; i++) {
    var key = keys[i];
    // Convert each item to a promise
    var promise = toPromise.call(this, obj[key]);
    // If it is a Promise, defer is called
    if (promise && isPromise(promise)) defer(promise, key);
    else results[key] = obj[key];
  }
  // Finally return all together
  return Promise.all(promises).then(function () {
    return results;
  });
	// Insert the Promise resolve result into the array
  function defer(promise, key) {
    // Assign undefined first
    results[key] = undefined;
    // Insert promises into the array, and save the result of Promise resolve into result
    promises.push(promise.then(function (res) { results[key] = res; })); }}Copy the code

IsPromise, isGenerator, isGeneratorFunction, isObject

// Check whether this is a promise
function isPromise(obj) {
  return 'function'= =typeof obj.then;
}
// Determine if it is a generator iterator object
function isGenerator(obj) {
  return 'function'= =typeof obj.next && 'function'= =typeof obj.throw;
}
// Check whether it is a generator function
function isGeneratorFunction(obj) {
  var constructor = obj.constructor;
  if (!constructor) return false;
  if ('GeneratorFunction'= = =constructor.name| | 'GeneratorFunction'= = =constructor.displayName) return true;
  return isGenerator(constructor.prototype);
}
// Determine whether it is an object
function isObject(val) {
  return Object == val.constructor;
}
Copy the code

Co. wrap function

Convert the Generator function to a promise function. Reusable, similar to cache function functions.

By virtue of higher-order functions, a new function createPromise is returned, and the parameters passed to it are imported into the Generator function.

co.wrap = function (fn) {
  createPromise.__generatorFunction__ = fn;
  return createPromise;
  function createPromise() {
    return co.call(this, fn.apply(this.arguments)); }};🌰 / / examples

// Check the Chinese name 1
co(function* () {
  const chineseName = yield searchChineseName('tom')
  return chineseName
})
// Check Chinese name 2
co(function* () {
  const chineseName = yield searchChineseName('jarry')
  return chineseName
})
// Can not reuse, reuse through co. Wrap
const getChineseName = co.wrap(function* (name) {
  const filename = yield searchChineseName(name)
  return filename
})
getChineseName('tom').then(res= > {})
getChineseName('jarry').then(res= > {})
Copy the code

Co function export mode

module.exports = co['default'] = co.co = co;
Copy the code

The co function can be derived in many ways, so there are many ways to introduce it.

const co = require('co')
require('co').co
import co from 'co'
import * as co from 'co'
import { co } form 'co'
Copy the code

Simple implementation of async await

Thus, async await is also a generator sugar, just like the CO function. If you understand the above, the following code should not be difficult to understand.

function asyncToGenerator(generatorFunc) {
    return function() {
      const gen = generatorFunc.apply(this.arguments)
      return new Promise((resolve, reject) = > {
        function step(key, arg) {
          let generatorResult
          try {
            generatorResult = gen[key](arg)
          } catch (error) {
            return reject(error)
          }
          const { value, done } = generatorResult
          if (done) {
            return resolve(value)
          } else {
            return Promise.resolve(value).then(val= > step('next', val), err= > step('throw', err))
          }
        }
        step("next")}}}Copy the code

Simple summary

Through the study of CO source code, I have a deeper understanding of generaitor, iterators, generators, async await. After the interview, if I come across relevant self-executing generator, IT should not be a problem.

Write in the last

I’m Siwei, a front-end developer who started trying to export something to the community.

If there are any errors in this article, please correct them in the comments section. If this article helped you, please like 👍 and follow ❤️