co源码分析

145 阅读4分钟

co源码分析

前言

Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态。调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器对象(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 }

1. Generator 函数的异步应用

Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)。整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用yield语句注明。

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 }

2. co 模块

co 模块是著名程序员 TJ Holowaychuk 于 2013 年 6 月发布的一个小工具,用于 Generator 函数的自动执行。Generator 函数只要传入co函数,就会自动执行。co函数返回一个Promise对象,因此可以用then方法添加回调函数。

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);

3. co 模块的源码

co 函数接受 Generator 函数作为参数,返回一个 Promise 对象。

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

在 Promise 对象里面,co 先检查参数gen是否为 Generator 函数。如果是,就执行该函数,得到一个内部指针对象;如果不是就返回,并将 Promise 对象的状态改为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 || typeof gen.next !== 'function') return resolve(gen);
  });
}

然后 co 将 Generator 函数的内部指针对象的next方法,包装成onFulfilled函数。这主要是为了能够捕捉抛出的错误。

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 || typeof gen.next !== 'function') return resolve(gen);
    onFulfilled();
    function onFulfilled(res) {
      var ret;
      try {
        ret = gen.next(res);
      } catch (e) {
        return reject(e);
      }
      next(ret);
    }
  });
}

最后,就是关键的next函数,它会反复调用自身。

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 || typeof gen.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) {
       // 检查当前是否为 Generator 函数的最后一步,如果是就返回。
      if (ret.done) return resolve(ret.value);
      // 确保每一步的返回值,是 Promise 对象。
      var value = toPromise.call(ctx, ret.value);
      // 使用then方法,为返回值加上回调函数,然后通过onFulfilled函数再次调用next函数。
      if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
      // 在参数不符合要求的情况下将 Promise 对象的状态改为rejected,从而终止执行。
      return onRejected(
	new TypeError('You may only yield a function, promise, generator, array, or object, ' +'but the following object was passed: "' + String(ret.value) +'"'));}
  });
}

co源码里几个重要的函数

// 参数转化为Proimse
function toPromise(obj) {
  if (!obj) return obj;
  if (isPromise(obj)) return obj;
  //是generator function或generator,则用co调用,返回一个promise
  if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
  // 函数转化为promise
  if ('function' == typeof obj) return thunkToPromise.call(this, obj);
  // 数组转化为promise
  if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
  // 对象转化为promise
  if (isObject(obj)) return objectToPromise.call(this, obj);
  return obj;
}

//thunk函数转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);
    });
  });
}
// 数组转化为promise
function arrayToPromise(obj) {
  return Promise.all(obj.map(toPromise, this));
}
// 对象转化为promise
function objectToPromise(obj){
  //构造一个同类型对象
  var results = new obj.constructor();
  var keys = Object.keys(obj);
  var promises = [];
  for (var i = 0; i < keys.length; i++) {
    var key = keys[i];
    //对象里面的所有value转换为promise
    var promise = toPromise.call(this, obj[key]);
    //是promise,就通过defer方法将promise执行结果放到results中
    if (promise && isPromise(promise)) defer(promise, key);
    //不是promise,就仅记录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;
    }));
  }
}

//判断是promise
function isPromise(obj) {
  return 'function' == typeof obj.then;
}

//判断是generator
function isGenerator(obj) {
  return 'function' == typeof obj.next && 'function' == typeof obj.throw;
}

//判断是generator function,其返回值是generator
function isGeneratorFunction(obj) {
  var constructor = obj.constructor;
  if (!constructor) return false;
  if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' === constructor.displayName) return true;
  return isGenerator(constructor.prototype);
}

//判断是对象
function isObject(val) {
  return Object == val.constructor;
}

4. 参考文献

ECMAScript 6 入门