中间件的执行顺序
在 Koa 应用中,第一个中间件里的请求是1,响应是 2,第二个中间件里的请求是 3,响应是 4,那么最终输出的结果是 1324。下面将对这个现象进行解析。
1. 从 Generator 说起
先来演示下 Generator 的用法
function *a(){
console.log('第 1 个中间件的 before')
yield *b()
console.log('第 1 个中间件的 after')
}
function *b() {
console.log('业务逻辑')
}
function *hello(){
yield *a()
}
const it1 = hello();
console.log(it1.next()) // {value: undefined, done: true }
代码的执行结果如下
第 1 个中间件的 before
业务逻辑
第 1 个中间件的 after
从结果可以看出,a 这个 Generator 里的内容,yield 之前的语句是先执行的,yield 之后的语句是在 b 执行完成后才执行的。这就是回形针的最早体现。
2. 用 co 简化代码
使用 co 这个 Generator 可以简化 Generator 的执行过程。
const co = require('co')
function *a(){
console.log('第 1 个中间件的 before')
yield *b()
console.log('第 1 个中间件的 after')
}
function *b() {
console.log('业务逻辑')
}
co(function *(){
yield *a()
})
这段代码的执行结果和上面的一致。
3. 具体的 1342
下面将以 ‘1342’ 为例,显式下中间件的执行过程:
const co = require('co')
function *a(){
console.log('第一个中间件的 before 1')
yield *b()
console.log('第一个中间件的 after 2')
}
function *b(){
console.log(' 第二个中间件的 before 3')
yield *c()
console.log(' 第二个中间件的 after 4')
}
function *c(){
console.log(' 这里是业务逻辑')
}
co(function *(){
yield *a()
})
以下是执行代码的结果:
第一个中间件的 before 1
第二个中间件的 before 3
这里是业务逻辑
第二个中间件的 after 4
第一个中间件的 after 2
中间件的执行顺序是 a->b->c,但是最终的执行顺序是 a1 -> b3 -> c -> b4 -> a2,这其实反映了这节的核心:当两个中间件叠加使用时,假设第一个中间件里的请求是 1,响应是 2,;第二个中间件的请求是 3,响应是 4,那么具体的执行顺序是 1->3->4->2
4. 中间件的写法
首先,先来模拟一下 Koa 的中间件:
var co = require('co')
var compose = require('koa-compose')
function *a(){
console.log('第一个中间件的 before 1')
yield *b()
console.log('第一个中间件的 after 2')
}
function *b(){
console.log(' 第二个中间件的 before 3')
yield *c()
console.log(' 第二个中间件的 after 4')
}
function *c(){
console.log(' 这里是业务逻辑')
}
var stack = [a,b,c]
co(compose(stack));
koa-compose 是 Koa 中最核心的模块,用于组合对个中间件,最终合并成一个中间件。
探索原理
- 探索1
// app.js
var co = require('co')
module.exports = {
middleware: [],
use: function(fn){
this.middleware.push(fn)
return this;
},
callback: function(cb) {
const fn = this.compose(this.middleware)
co(fn).then(cb, function(err){
console.log(err.stack)
})
},
compose: function(middleware){
return function *(next) {
if (!next) {
next = function *(){}
}
var i = middleware.length;
while(i--) {
next = middleware[i].call(this, next)
}
}
return yield *next
}
}
测试代码如下
var app = reqiure('./app')
app.use(function *(next) {
console.log(1)
yield next;
console.log(2)
})
app.use(function *(next) {
console.log(3)
yield next
console.log(4)
})
app.callback() // 1342
探索 1 的代码只依赖了 co 模块,整体来说非常容易理解。
这里对重点代码进行解读
- callback
- 通过 this.compose 把 this.middleware 转变成一个名为 fn 的 Generator 函数。
- 通过 co 执行 fn
- co 返回的是 Promise 对象,所以 then 后面接了两个参数,其中 cb 是成功回调,后面的函数用于异常处理。
- compose
compose 的功能是将 compose([f1,f2...,fn]) 转化为 fn(...f2(f1(next()))), 最终返回一个 Generator 函数。
compose 返回的是 Generator ,因此它就需要一个 Generator 执行器,也就是上文出现的 co,来搭配使用。compose 其实就是 koa-compose 的核心功能,由此课件 koa-compose 的重要程度。
tip: co函数的作用是:将Generator函数转成一个promise对象。然后自动执行该函数的代码
- 探索 2
// app.js
var co = require('co')
var convert = require('koa-convert')
module.exports = {
middleware: [],
use: function(fn) {
this.middleware.push(fn)
return this
},
callback: function(){
const fn = convert.compose(this.middleware)
const ctx = {}
fn(ctx).then(() => {})
}
}
tip: koa-convert最主要的作用是:将koa1包中使用的Generator函数转换成Koa2中的async函数。更准确的说是将Generator函数转换成使用co包装成的Promise对象。然后执行对应的代码。当然该包中也提供了back方法,也可以把koa2中的async函数转换成koa1包中的Generator函数。
测试代码如下:
var app = require('./app')
app.use((ctx, next) => {
console.log(1)
return next().then(() => {
console.log(2)
})
})
app.use(function *(ctx, next) => {
console.log(3)
yield next
console.log(4)
})
app.callback() // 1342
回溯机制
在 Express 里,中间件请求进行处理,但无法对响应进行处理。
但是对于 Koa 而言,中间件能对请求做出处理,也能对响应作出处理。
1. 回形针的实现: compose
下面是具体代码:
compose: function(middleware) {
return function(content, next) {
let index = -1;
return dispatch(0)
function dispatch(i) {
if(i<= index) {
return Promise.reject(new Error('next() called multiple tiems'))
}
index = i
const fn = middleware[i] || next
if (!fn) {
return Promise.resolve()
}
try {
return Promise.resolve(fn(content, function next() {
return dispatch(i +1)
}))
} catch(err) {
return Promise.reject(err)
}
}
}
}
可以看到,最终返回的结果是 Promise,这样子可以很好的配合 async 和 await。
3. 从 Koa v1 到 Koa2 v2
Koa v1 的中间件机制其实就是将 compose([f1,f2....,fn]) 转换为 fn(....f2(f1(next))), 最终返回值是一个生成器函数。而 Koa v2 将其转化为 fn(...fn-1(fn(noop())))); 最终返回的是 function (content, next){}。