koa框架异步返回值的操做(co,koa-compose)

最近在作demo的时候使用了koa框架,本身作了一个静态服务器,首先判断访问文件是否存在,在回调函数中设置了this.body,run以后,各类404,花了N长的时间把koa-compose和co模块看了下,只能说本身终于有了一个比较浅显的认识了。git

首先咱们看下koa-compose的代码,就短短的十几行。github

function compose(middleware){
  return function *(next){
    var i = middleware.length;
    var prev = next || noop();
    var curr;

    while (i--) {
      curr = middleware[i];
      prev = curr.call(this, prev);
    }

    yield *prev;
  }
}



function *noop(){}
View Code

在koa框架中,中间件都是generator函数,而koa-compose的做用就是将generator函数关联起来。api

如代码所示,该函数返回一个generator函数,在返回的generator函数中,从最后一个中间件开始,prev最初赋值为一个空的generator函数。在while函数中,curr赋值为最后一个generator函数,而后用koa的ctx去调用最后一个generator函数,并将最初的prev即一个空的generator函数做为参数传入(最后一个中间件通常都没有yield,因此不作过多讨论),返回一个generator对象,并将其赋值给prev,接着调用倒数第二个中间件,并将prev当作参数传入到中间件的next参数中,而后将倒数第二个中间件的generator对象赋值给prev。分析下此时的prev对象,此时prev的next函数执行的是倒数第二个中间件的代码并返回倒数第一个中间件的generator对象(此时讨论的是generator函数只有一个yield next)。一直while后,知道prev为第一个中间件的generator对象,并用yield *prev返回。promise

接着看co的几个核心代码服务器

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 onRejected(err) {
      var ret;
      try {
        ret = gen.throw(err);
      } catch (e) {
        return reject(e);
      }
      next(ret);
    }



    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) + '"'));
    }
  });
}



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函数拿到koa-comprose返回的generator函数(或者对象)后,会return一个promise,在promise的function中app

调用了onFulfilled函数。在该函数中,会调用gen的next方法,这样会执行第一个中间件yield和以前的的代码,并返回yield 的Object(不特指一个new Object),并赋值给ret, 而后调用next方法,并将ret对象传入。若是第一个generator迭代完成(即你的gen中没有yield),再调用没有yield的gen所在的promise的reslove方法,不然用ctx调用toPromise方法,并将第二个中间件的generator对象放入参数位置。在toPromise方法中,若是放入的是一个undefined,则直接返回undefined,不然将其放入isPromise方法中,让咱们看看isPromise方法框架

function isPromise(obj) {
  return 'function' == typeof obj.then;
}

只有简单的判断对象是否有then方法,若是有的话,在toPromise中直接返回obj,不然的话,判断obj是generator函数仍是generator对象,若是是的话,则从新调用co函数,并将obj当作参数传入,假设如今只有两个中间件。而后第二个中间件的generator对象,调用next并获得一个ret对象,并用该ret对象,将其做为参数传入next中,由于最后一个中间件没有yield next,因此第二个中间件generator所在的promise对象调用resolve方法,至此第二个中间件执行完毕,并将第二个中间件所在的promise经过 if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj); 返回第一个中间件的next方法中,并将其赋值给value。 而后给该promise添加then中的逻辑,此时开始执行第一个中间件剩余部分代码.若是第二个中间件(then中获得的值为迭代方法return的值)。koa

function co(gen) {
var ctx = this;
var args = slice.call(arguments, 1)

// we wrap everything in a promise to avoid promise chaining,
// which leads to memory leak errors.
// see https://github.com/tj/co/issues/180
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();

/**
* @param {Mixed} res
* @return {Promise}
* @api private
*/

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

/**
* @param {Error} err
* @return {Promise}
* @api private
*/

function onRejected(err) {
var ret;
try {
ret = gen.throw(err);
} catch (e) {
return reject(e);
}
next(ret);
}

/**
* Get the next value in the generator,
* return a promise.
*
* @param {Object} ret
* @return {Promise}
* @api private
*/

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) + '"'));
}
});
}

/**
* Convert a `yield`ed value into a promise.
*
* @param {Mixed} obj
* @return {Promise}
* @api private
*/

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;}在上面咱们只讨论了中间件之间的yield,若是在yield next中间加入了yeild function * b(){}或者yeild new Promise该如何?仍是以两个中间件为例,在第二个中间件yield Promise对于koa-compose没有太大变化在co中,第一个中间件的执行没有变化,在第二次调用co并把第二个中间件的迭代对象放入的时候,当执行next方法的时候,会将一个value为promise的对象传入到next方法中,在toPromise中,由于obj自己为一个promise,会直接返回promise避免了再一次调用co。此时co执行第二次中,next方法中的value为该promise,而后给该promise添加then方法,待该Promise调用了reslove后,在该then方法中,再一次开始第二个中间件剩下代码的执行。因此在koa框架中若是想要异步设置this.body,须要yield Promise(function(re,rej){})并在function中设置this.body,当异步执行完成后,调用re方法,传入本身想要传递的值,koa框架继续执行剩余代码。第一次写这么长的,感受有点渣...
相关文章
相关标签/搜索