Co source code analysis

preface

Generator functions are an asynchronous programming solution provided by ES6 with completely different syntactic behavior from traditional functions. The Generator function is a normal function, but has two characteristics. There is an asterisk between the function keyword and the function name. Second, inside the function body, yield expressions are used to define different internal states. When a Generator function is called, it does not execute and returns not the result of the function’s execution, but a pointer Object to the internal state, known as an Iterator Object.

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();
hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'world', done: false }

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }
Copy the code

1. Asynchronous application of Generator functions

Generator functions are an ES6 implementation of coroutines with the best feature of surrendering execution of the function (i.e., suspending execution). The whole Generator function is a encapsulated asynchronous task, or a container for asynchronous tasks. Where asynchronous operations need to be paused, use the yield statement.

function* gen(x) {
  var y = yield x + 2;
  return y;
}

var g = gen(1);
g.next() // { value: 3, done: false }
g.next() // { value: undefined, done: true }
Copy the code

2. Co module

The CO module is a small tool released in June 2013 by TJ Holowaychuk, a well-known programmer, for automatic execution of Generator functions. The Generator functions are automatically executed as soon as the CO function is passed in. The CO function returns a Promise object, so callbacks can be added using the then method.

var co = require('co');

co(function* (){
  // yield any promise
  var result = yield Promise.resolve(true);
}).catch(onerror);

co(function* (){
  // resolve multiple promises in parallel
  var a = Promise.resolve(1);
  var b = Promise.resolve(2);
  var c = Promise.resolve(3);
  var res = yield [a, b, c];
  console.log(res);
  // => [1, 2, 3]
}).catch(onerror);

Copy the code

3. Source code of co module

The co function takes a Generator function as an argument and returns a Promise object.

function co(gen) {
  var ctx = this;
  var args = slice.call(arguments.1);
  return new Promise(function(resolve, reject) {}); }Copy the code

Inside the Promise object, CO first checks if gen is a Generator. If so, the function is executed to get an internal pointer object; If not, return and change the state of the Promise object to Resolved.

function co(gen) {
  var ctx = this;
  var args = slice.call(arguments.1);
  return new Promise(function(resolve, reject) {
    if (typeof gen === 'function') gen = gen.apply(ctx, args);
    if(! gen ||typeofgen.next ! = ='function') return resolve(gen);
  });
}
Copy the code

Then CO wraps the next method of the internal pointer object of the Generator function as ondepressing function. This is mainly to be able to catch thrown errors.

function co(gen) {
  var ctx = this;
  var args = slice.call(arguments.1);
  return new Promise(function(resolve, reject) {
    if (typeof gen === 'function') gen = gen.apply(ctx, args);
    if(! gen ||typeofgen.next ! = ='function') return resolve(gen);
    onFulfilled();
    function onFulfilled(res) {
      var ret;
      try {
        ret = gen.next(res);
      } catch (e) {
        returnreject(e); } next(ret); }}); }Copy the code

Finally, there is the crucial Next function, which calls itself repeatedly.

function co(gen) {
  var ctx = this;
  var args = slice.call(arguments.1);
  return new Promise(function(resolve, reject) {
    if (typeof gen === 'function') gen = gen.apply(ctx, args);
    if(! gen ||typeofgen.next ! = ='function') return resolve(gen);
    onFulfilled();
    function onFulfilled(res) {
      var ret;
      try {
        ret = gen.next(res);
      } catch (e) {
        return reject(e);
      }
      next(ret);
    }
    function next(ret) {
       // Check if the current is Generator and return if so.
      if (ret.done) return resolve(ret.value);
      // Make sure that the return value of each step is a Promise object.
      var value = toPromise.call(ctx, ret.value);
      // Use the then method to add the callback function to the return value, and then call the next function again via the ondepressing function.
      if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
      // Change the state of the Promise object to Rejected if the parameters do not meet the requirements.
      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

Co source code in several important functions

// The parameter is converted to Proimse
function toPromise(obj) {
  if(! obj)return obj;
  if (isPromise(obj)) return obj;
  // generator function or generator, call with co and return a promise
  if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
  // The function is converted to promise
  if ('function'= =typeof obj) return thunkToPromise.call(this, obj);
  // The array is converted to promise
  if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
  // The object is converted to promise
  if (isObject(obj)) return objectToPromise.call(this, obj);
  return obj;
}

//thunk goes to promise
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);
    });
  });
}
// The array is converted to promise
function arrayToPromise(obj) {
  return Promise.all(obj.map(toPromise, this));
}
// The object is converted to promise
function objectToPromise(obj){
  // Construct an object of the same type
  var results = new obj.constructor();
  var keys = Object.keys(obj);
  var promises = [];
  for (var i = 0; i < keys.length; i++) {
    var key = keys[i];
    // All values in the object are converted to promises
    var promise = toPromise.call(this, obj[key]);
    // If it is a promise, use the defer method to put the promise execution results into results
    if (promise && isPromise(promise)) defer(promise, key);
    // Not a promise, just a value
    else results[key] = obj[key];
  }
  return Promise.all(promises).then(function () {
    return results;
  });

  function defer(promise, key) {
    // predefine the key in the result
    results[key] = undefined;
    promises.push(promise.then(function (res) { results[key] = res; })); }}// Judge is promise
function isPromise(obj) {
  return 'function'= =typeof obj.then;
}

// It is a generator
function isGenerator(obj) {
  return 'function'= =typeof obj.next && 'function'= =typeof obj.throw;
}

// The return value is 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);
}

// Judgments are objects
function isObject(val) {
  return Object == val.constructor;
}
Copy the code

4. References

ECMAScript introduction to 6