原文地点
近来几天花了比较长的时刻在koa(1)的源码剖析上面,首次看的时刻,被中间件实行那段整的晕乎乎的,完整不晓得所以,再次看,彷佛邃晓了些什么,再重复看,我去,险些神了,险些泣如雨下,险些丧尽天良啊!!!
下面的例子会在掌握台中打印出一些信息(详细打印出什么?能够猜猜?),然后返回
hello world
。
let koa = require('koa')
let app = koa()
app.use(function * (next) {
console.log('generate1----start')
yield next
console.log('generate1----end')
})
app.use(function * (next) {
console.log('generate2----start')
yield next
console.log('generate2----end')
this.body = 'hello world'
})
app.listen(3000)
用过koa的同砚都晓得增加中间件的体式格局是运用koa实例的use
要领,并传入一个generator函数,这个generator函数能够接收一个next
(这个next究竟是啥?这里先不申明,在背面会细致申明)。
实行use干了嘛
这是koa的组织函数,为了没有其他信息的滋扰,我去除了一些临时用不到的代码,这里我们把目光聚焦在middleware
这个数组即可。
function Application() {
// xxx
this.middleware = []; // 这个数组就是用来装一个个中间件的
// xxx
}
接下来我们要看use要领了
一样去除了一些临时不必的代码,能够看到每次实行use要领,就把表面传进来的generator函数push到middleware数组中
app.use = function(fn){
// xxx
this.middleware.push(fn);
// xxx
};
好啦!你已晓得koa中是预先经由历程use要领,将要求可能会经由的中间件装在了一个数组中。
接下来我们要最先本文的重点了,当一个要求到来的时刻,是怎样经由中间件,怎样跑起来的
起首我们只需晓得下面这段callback
函数就是要求到来的时刻实行的回调即可(一样只管去除了我们不必的代码)
app.callback = function(){
// xxx
var fn = this.experimental
? compose_es7(this.middleware)
: co.wrap(compose(this.middleware));
// xxx
return function(req, res){
// xxx
fn.call(ctx).then(function () {
respond.call(ctx);
}).catch(ctx.onerror);
// xxx
}
};
这段代码能够分红两个部份
要求前的中间件初始化处置惩罚部份
要求到来时的中间件运转部份
我们分部份来讲一下
var fn = this.experimental
? compose_es7(this.middleware)
: co.wrap(compose(this.middleware));
这段代码对experimental
做了下推断,假如设置为了true
那末koa中将能够支撑传入async函数,不然就实行co.wrap(compose(this.middleware))
。
只要一行初始化中间件就做完啦?
我晓得koa很屌,但也别这么屌好不好,所以说评价一个好的程序员不是由代码量决议的
我们来看下这段代码到底有什么奇异的处所
compose(this.middleware)
把装着中间件middleware
的数组作为参数传进了compose
这个要领,那末compose做了什么事呢?实在就是把底本毫无关联的一个个中间件给首尾串起来了,因而他们之间就有了千丝万缕的联络。
function compose(middleware){
return function *(next){
// 第一次获得next是由于*noop天生的generator对象
if (!next) next = noop();
var i = middleware.length;
// 从后往前最先实行middleware中的generator函数
while (i--) {
// 把后一个中间件获得的generator对象传给前一个作为第一个参数存在
next = middleware[i].call(this, next);
} return yield *next;
}
}
function *noop(){}
笔墨解释一下就是,compose将中间件从末了一个最先处置惩罚,并一向往前直到第一个中间件。个中异常症结的就是将后一个中间件获得generator对象作为参数(这个参数就是文章开首说到的next啦,也就是说next实际上是一个generator对象
)传给前一个中间件。固然末了一个中间件的参数next
是一个空的generator函数天生的对象。
我们本身来写一个简朴的例子申明compose是怎样将多个generator函数串连起来的
function * gen1 (next) {
yield 'gen1'
yield * next // 最先实行下一个中间件
yield 'gen1-end' // 下一个中间件实行完成再继承实行gen1中间件的逻辑
}
function * gen2 (next) {
yield 'gen2'
yield * next // 最先实行下一个中间件
yield 'gen2-end' // 下一个中间件实行完成再继承实行gen2中间件的逻辑
}
function * gen3 (next) {
yield 'gen3'
yield * next // 最先实行下一个中间件
yield 'gen3-end' // 下一个中间件实行完成再继承实行gen3中间件的逻辑
}
function * noop () {}
var middleware = [gen1, gen2, gen3]
var len = middleware.length
var next = noop() // 提供给末了一个中间件的参数
while(len--) {
next = middleware[len].call(null, next)
}
function * letGo (next) {
yield * next
}
var g = letGo(next)
g.next() // {value: "gen1", done: false}
g.next() // {value: "gen2", done: false}
g.next() // {value: "gen3", done: false}
g.next() // {value: "gen3-end", done: false}
g.next() // {value: "gen2-end", done: false}
g.next() // {value: "gen1-end", done: false}
g.next() // {value: undefined, done: true}
看到了吗?中间件被串起来以后实行的递次是
gen1 -> gen2 -> gen3 -> noop -> gen3 -> gen2 -> gen1
从而首尾相连,进而发生了关联?。
经由历程compose处置惩罚后返回了一个generator函数。
co.wrap(compose(this.middleware))
一切上述代码能够明白为
co.wrap(function * gen ())
好,我们再看看co.wrap
做了什么,逐步地一步步靠近了哦
co.wrap = function (fn) {
createPromise.__generatorFunction__ = fn;
return createPromise;
function createPromise() {
return co.call(this, fn.apply(this, arguments));
}
}
能够看到co.wrap
返回了一个一般函数createPromise
,这个函数就是文章开首的fn
啦。
var fn = this.experimental
? compose_es7(this.middleware)
: co.wrap(compose(this.middleware));
前面已说完了,中间件是怎样初始化的,即假如由不相干到关联密切了,接下来最先说要求到来时,初始化好的中间件是怎样跑的。
fn.call(ctx).then(function () {
respond.call(ctx);
}).catch(ctx.onerror);
这一段就是要求到来手即将要经由的中间件实行部份,fn实行以后返回的是一个Promise,koa经由历程注册胜利和失利的回调函数来离别处置惩罚要求。
让我们回到
co.wrap = function (fn) {
// xxx
function createPromise() {
return co.call(this, fn.apply(this, arguments));
}
}
createPromise
内里的fn就是经由compose处置惩罚中间件后返回的一个generator函数,那末实行以后拿到的就是一个generator对象了,并把这个对象传经典范的co内里啦。假如你需要对co的源码相识迎接检察昨天写的走一步再走一步,揭开co的神奇面纱,好了,接下来就是看co内里怎样处置惩罚这个被compose处置惩罚过的generator对象了
再回忆一下co
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) + '"'));
}
});
}
我们直接看一下onFulfilled
,这个时刻第一次进co的时刻由于已是generator对象所以会直接实行onFulfilled()
function onFulfilled(res) {
var ret;
try {
ret = gen.next(res);
} catch (e) {
return reject(e);
}
next(ret);
}
而gen.next
恰是用于去实行中间件的营业逻辑,当碰到yield语句的时刻,将紧随厥后的效果返回赋值给ret
,一般这里的ret,就是我们文中说道的next
,也就是当前中间件的下一个中间件。
拿到下一个中间件后把他交给next
去处置惩罚
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) + '"'));
}
当中间件实行完毕了,就把Promise的状况设置为胜利。不然就将ret
(也就是下一个中间件)再用co包一次。主要看toPromise
的这几行代码即可
function toPromise(obj) {
// xxx
if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
// xxx
}
注重噢toPromise
这个时刻的返回值是一个Promise,这个异常症结,是下一个中间件实行完成以后回溯到上一个中间件中缀实行处继承实行的症结
function next(ret) {
// xxx
var value = toPromise.call(ctx, ret.value);
// 即经由历程前面toPromise返回的Promise完成,当后一个中间件实行完毕,回退到上一个中间件中缀处继承实行
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
// xxx
}
看到这里,我们能够总结出,险些koa的中间件都会被co给包装一次,而每个中间件又能够经由历程Promise的then去监测厥后一个中间件是不是完毕,后一个中间件完毕后会实行前一个中间件用then监听的操纵,这个操纵就是实行该中间件yield next背面的那些代码
打个比如:
当koa中接收到一个要求的时刻,要求将经由两个中间件,离别是中间件1
和中间件2
,
中间件1
// 中间件1在yield 中间件2之前的代码
yield 中间件2
// 中间件2实行完成以后继承实行中间件1的代码
中间件2
// 中间件2在yield noop中间件之前的代码
yield noop中间件
// noop中间件实行完成以后继承实行中间件2的代码
那末处置惩罚的历程就是co会立时挪用onFulfilled来实行中间件1前半部份代码,碰到yield 中间件2
的时刻获得中间件2generator对象,紧接着,又把这个对象放到co内里继承实行一遍,以此类推下去晓得末了一个中间件(我们这里的指的是谁人空的noop中间件)实行完毕,继而立时挪用promise的resolve要领示意完毕,ok,这个时刻中间件2监听到noop实行完毕了,立时又去实行了onFulfilled来实行yield noop中间件后半部份代码,好啦这个时刻中间件2也实行完毕了,也会立时挪用promise的resolve要领示意完毕,ok,这个时刻中间件1监听到中间件2实行完毕了,立时又去实行了onFulfilled来实行yield 中间件2后半部份代码,末了中间件悉数实行完了,就实行respond.call(ctx);
啊 啊 啊好绕,不过逐步看,细致想,照样能够想邃晓的。用代码示意这个历程有点相似
new Promise((resolve, reject) => {
// 我是中间件1
yield new Promise((resolve, reject) => {
// 我是中间件2
yield new Promise((resolve, reject) => {
// 我是body
})
// 我是中间件2
})
// 我是中间件1
});
罗里吧嗦说了一大堆,也不晓得有无把实行道理说邃晓。
假如对你明白koa有些许协助,不介意的话,点击源码地点点颗小星星吧
假如对你明白koa有些许协助,不介意的话,点击源码地点点颗小星星吧
假如对你明白koa有些许协助,不介意的话,点击源码地点点颗小星星吧
源码地点