您的位置:首页 > 其它

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

2016-01-27 12:01 176 查看
最近在做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前的代码,并返回一个对象,然后调用next方法,并将ret对象传入。如果第一个generator迭代完成,再调用返回的第一个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框架继续执行剩余代码。

第一次写这么长的,感觉有点渣...
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: