hawx1993 / tech-blog

📦My personal tech blog,not regularly update
http://sf.gg/u/trigkit4/articles
339 stars 30 forks source link

co函数库源码解析 #18

Open hawx1993 opened 7 years ago

hawx1993 commented 7 years ago

co 函数库是一个中间产品,co是coroutine的缩写,即协同程序。co可以说是给generator增加了promise实现。co是利用Generator的方式实现了async/await

co函数库对外暴露的方法只有两个:

其中,fn *代表Generator函数。看一个官方文档中的例子:

co(function* () {
  return yield Promise.resolve(true);
}).then(function (val) {
  console.log(val);
}, function (err) {
  console.error(err.stack);
});

co函数接收一个Generator 函数作为参数。执行co函数的时候,生成器函数内部的逻辑像async函数调用时一样被执行。不同之处只是这里的await变成了yield(产出)。async和co一样,都返回了Promise对象,都可以链式调用.then()方法

关于Generator 函数,我们可以看一个例子:

function* generatorFunction() {
    console.log(1);
    var a = yield 'a';
    console.log(2);
    var b = yield 'b';
    console.log(3);
    var c = yield 'c';
    return c;
}

var gen = generatorFunction();
gen.next();    // 打印 1,返回 {value: "a", done: false}
gen.next();    // 打印 2,返回 {value: "b", done: false}
gen.next();    // 打印 3,返回 {value: "c", done: false}
gen.next('d'); // 没有打印,返回 {value: "d", done: true}
gen.next();    // 没有打印,返回 {value: undefined, done: true}

gen.next()的返回对象格式如下:

{
   done: {boolean},
   value: {*}
}

done 表示迭代器是否结束,value 表示yield后面语句的返回值。co用到的一些特性:

co内部定义了co函数,通过返回new Promise()对象,实现了链式调用。

一个Generator内部可能有多个yield,一个yield结束之后会执行下一个yield,这个过程通过递归实现:

function onFulfilled(res) {
      var ret
      try {
        ret = gen.next(res);
      } catch (e) {
        return reject(e);
      }
      next(ret);
      return null
    }

reject则通过throw来将错误抛出

对于next函数的实现:

//获取generator next的值,并返回Promise
function next(ret) {
      if (ret.done) return resolve(ret.value);//如果迭代器结束,则将值作为resolve的参数传递出去,可以在.then中获取到
      var value = toPromise.call(ctx, ret.value);//ctx代表当前环境对象,window or global
      if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
      return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
        + 'but the following object was passed: "' + String(ret.value) + '"'));
    }
//将yield产出的值转为Promise的值
function toPromise(obj) {
  if (!obj) return obj;
  if (isPromise(obj)) return obj;
  if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
  if ('function' == typeof obj) return thunkToPromise.call(this, obj);
  if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
  if (isObject(obj)) return objectToPromise.call(this, obj);
  return obj;
}

所以其实co函数库只是内部封装了Generator函数和自动执行器(通过递归调用实现),并返回了Promise对象。使其可以和async一样,做到“同步调用,异步执行”

co.wrap(fn *)

传入一个generator,返回一个普通函数,这个函数会返回一个Promise。

co.wrap = function (fn) {
  createPromise.__generatorFunction__ = fn;//[GeneratorFunction: callee]

  return createPromise;
  function createPromise() {
    return co.call(this, fn.apply(this, arguments));
  }
};

由于声明式函数具有提升作用,所以createPromise可以在上面调用,而createPromise函数则是:

{ [Function: createPromise] __generatorFunction__: [GeneratorFunction: callee$0$0] }

其中,fn.apply(this, arguments)则是:

GeneratorFunctionPrototype { _invoke: [Function: invoke] }