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框架繼續執行剩餘代碼。第一次寫這麼長的,感受有點渣...
相關文章
相關標籤/搜索