koa框架異步返回值的操作(co,koa-compose)


最近在做demo的時候使用了koa框架,自己做了一個靜態服務器,首先判斷訪問文件是否存在,在回調函數中設置了this.body,run之后,各種404,花了N長的時間把koa-compose和co模塊看了下,只能說自己終於有了一個比較淺顯的認識了。

首先我們看下koa-compose的代碼,就短短的十幾行。

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函數關聯起來。

如代碼所示,該函數返回一個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返回。

接着看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中

調用了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的值)。

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框架繼續執行剩余代碼。

第一次寫這么長的,感覺有點渣...


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM