What can I yield?

浏览器支持情况:Enabled by default in desktop Chrome 39 

一句话回答这个问题是:Promise,Thunks。为什么没有Generators?因为Generators被转为了Promise。

Koa使用的是co库。所以在co中定义yield后的语句是什么,koa就默认支持,比如:Promise,Thunks,Generators,Arrays,Object,注意这里的Array和Object的每一项,每一个值都必须是Promise,Thunks,Generators的一种。同时在co里的generator里的yield后面也只能是Promise,Thunks,Generators,最后所有Promise,Thunks,Generators,Arrays,Object都封装成了Promise。

流程图如下:

co内部封装了onFulfilledonRejected函数,当yield右侧的promise resolve之后,则会调用onFullfield函数,onFullfield有一个关键的地方是:会调用gen.next(res)方法,用以给yield表达式赋值并执行下一次迭代。

co到底做了什么?我们先来看一段源码。

//这是一个thunk函数,只不过是用做倒计时
function sleep(msg) {
    return function(done){
        setTimeout(function (){
            done("", msg)
        }, 1000);
    }
}

//Generator
function * G(msg){
    return yield sleep(msg);
}

//Promise
function P(msg){
    return new Promise(function (resolve, reject){
        setTimeout(function (){
            resolve(msg);
        }, 1000)
    });
}

//Thunk
function Thunk(msg){
    return function (callback){
        setTimeout(function (){
            callback("", msg);
        }, 1000)
    }
}

//step 1
co(function *(){
    console.log("before co");

    //step 2
    var generatorResult = yield G("Generator");
    console.log("after result " + generatorResult);

    //step 3
    var promiseResult = yield P("Promise");
    console.log("after result " + promiseResult);
    
    var thunkResult = yield Thunk("Thunk");
    console.log("after result " + thunkResult);

    console.log("after co");
});

//console.log 结果
2016-05-26 11:38:57.578 before co
2016-05-26 11:38:58.584 index.js:37 after result Generator
2016-05-26 11:38:59.588 index.js:40 after result Promise
2016-05-26 11:39:00.590 index.js:43 after result Thunk
2016-05-26 11:39:00.591 index.js:45 after co

后面的逻辑:

  • step 1:co函数拿到参数Generator后封装成一个Promise,并且立即执行onFulfilled方法,在onFulfilled方法里会调用co的Generator的next方法,获取到后面的表达式结果,并且传递给next方法。next方法会把传递值都转为promise
function onFulfilled(res) {
  var ret;
  try {
    ret = gen.next(res);
  } catch (e) {
    return reject(e);
  }
  
  next(ret);
  return null;
}
  • step 2:调用next方法后会获得G("Generator")的返回值,判断执行后是一个Generator,然后再继续step 1逻辑,这时再调用onFulfilled方法后next方法获得是一个Thunk函数,并且把Thunk通过方法thunkToPromise封装为Promise,注意这个thunkToPromise方法
  //co.js
  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);
      });
    });
  }

这个Thunk的参数是一个function,并且返回值第一个是err,第二个真实的值,这个是node回调规范,当回调执行后,就会触发resolve方法,在resolve里已经注册了onFulfilled方法,会触发Generator的next方法,把值通过Generator的next机制传递到generatorResult变量里。这样step2就完成了。

注意co里的next方法很有意思,注意第四行代码。

  //co.js
  function next(ret) {
    if (ret.done) return resolve(ret.value);
    var value = toPromise.call(ctx, ret.value);
    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) + '"'));
  }
  • step 3,这一步就是step2的简化版,当promise的resolve方法执行后,会触发onFulfilled方法,继续把值通过Generator的next机制传递到promiseResult变量里

  • 同理 step 4

知道这些就可以很好的使用co方法,来做到同步书写代码了。我们可以在yield后面传递Array,Object看完源码就一目了然。

Array:是把数组每一项转化为Promise,然后用all方法

​```javascript
//co.js
function arrayToPromise(obj) {
return Promise.all(obj.map(toPromise, this));
}


Object:遍历对象,把每一项推到数组里,用Promise.all方法封装

```javascript
//co.js
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];
    var promise = toPromise.call(this, obj[key]);
    if (promise && isPromise(promise)) defer(promise, key);
    else results[key] = obj[key];
  }
  //遍历对象,把每一项推到数组里,用Promise.all方法封装
  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;
    }));
  }
}
posted @ 2017-02-20 13:36  buzzjan  阅读(319)  评论(0编辑  收藏  举报