koa的中间件执行的流程控制,代码的是非常精妙的。由下面的一张洋葱模型的图来形容,记住这张图。
为什么是这样子的图,下面我们有一个例子来描述一下
const Koa = require('koa')
const app = new Koa()
// fn1
app.use(async (ctx, next) => {
console.log('fn1-1')
next()
console.log('fn1-2')
})
// fn2
app.use(async (ctx, next) => {
console.log('fn2-1')
next()
console.log('fn2-2')
})
// fn3
app.use(async (ctx, next) => {
console.log('fn3-1')
next()
console.log('fn3-2')
})
app.listen(4002)
// fn1-1、fn2-1、fn3-1、fn3-2、fn2-2、fn1-2
上面的这个例子,顺序打印出来的是fn1-1、fn2-1、fn3-1、fn3-2、fn2-2、fn1-2
,现在只知道,调用next()
函数就会把控制流程就跳到下一个中间件,知道执行所有完之后然后再逐步向上执行前一个next
后面的代码。这根跟洋葱有很大的相像似性(如果你愿意一层一层一层的剥开我的心~~~)。
探索
但是其中的原理是什么呢??下面我们一步步去探索。
首先是调用 app.use(fn)
这行代码,这行代码在源码里面,删除一些代码判断,是这样子的
constructor() {
super();
this.middleware = [];
}
use(fn) {
this.middleware.push(fn);
return this;
}
就是把所有函数push
到一个middleware
的数组之中,这个use
就是专门干这中勾当的。
好了知道use
的作用了,执行了use
之后 我们的middleware
中就有很多中间件函数了,下面我们继续看下去。
然后执行到 app.listen
函数之后,代码如下
listen(...args) {
// 创建一个server
const server = http.createServer(this.callback());
return server.listen(...args);
}
我们看到里么有个this.callback()
执行函数,然后我们跳到这个函数里面。
callback() {
// 我们看这里
const fn = compose(this.middleware);
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res);
// 这个节点我们请记住下面这一行代码
return this.handleRequest(ctx, fn);
};
return handleRequest;
}
这个callback
函数里面,执行了compose
函数,并且把middleware
数组作为参数传递进去。
执行到了compose
函数,下面我们就看看compose
里面有什么。
compose
函数就是一开始引用了koa-compose
模块,简化之后发现里面的代码如下,简化后就简简单单的20几行代码,后面会详细解释下面的代码。
function compose (middleware) {
return function (context, next) {
let index = -1
return dispatch(0)
function dispatch (i) {
index = i
let fn = middleware[i]
if (i === middleware.length) fn = next
if (!fn) return Promise.resolve()
try {
return Promise.resolve(fn(context, function next () {
return dispatch(i + 1)
}))
} catch (err) {
return Promise.reject(err)
}
}
}
}
执行这个compose
返回一个函数,这也是最核心的一个函数。注意这是上面的callback
调用的。得到一个fn
函数
看上面的callback
调用的
然后执行到this.handleRequest(ctx, fn);
这个函数吧ctx
和fn
(这个就是上面compose返回的函数)作为参数,传入到this.handleRequest中。 代码如下。
handleRequest(ctx, fnMiddleware) {
return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}
到这里才真正的执行了compose
返回的函数,把ctx
传进去。然后我们继续看这个函数fnMiddleware(ctx)
,其实就是下面这样子的。
function (context, next) {
// 设置了一个出事索引值
let index = -1
// 调用dispatch函数,开始时候传0进去
return dispatch(0)
// 声明dispatch函数,
function dispatch (i) {
// 把传进来的赋值给index
index = i
// 拿出middleware第i个中间件函数,赋值给fn
let fn = middleware[i]
// 判断如果i 等于middleware的长度 就把next 赋值给 fn
if (i === middleware.length) fn = next
// 如果fn是假的 return return Promise.resolve()
if (!fn) return Promise.resolve()
try {
// 返回一个Promise.resolve, 同时执行fn, 也就是中间件,把next 函数传递到fn里面
return Promise.resolve(fn(context, function next () {
// 递归调用自己
return dispatch(i + 1)
}))
} catch (err) {
return Promise.reject(err)
}
}
}
上面的代码是这个部分的精华。这里详细的说一下,首先定义了一个index
和dispatch
函数, 然后一开始调用dispatch(0)
函数,里面把0赋值给了index
,然后从middleware
的数组(例子中我们有三个中间件函数)中拿到第0
个中间件函数,赋值给fn
,经过两个if
都不符合条件,然后执行
return Promise.resolve(fn(context, function next () {
// 递归调用自己
return dispatch(i + 1)
}))
这里的执行fn
中间件函数,并且把ctx
和 function next () { return dispatch(i + 1) })
作为参数传递进去。这个时候代码如下一幕了然
app.use(async (ctx, next) => {
console.log('fn1-1')
next() // 执行传入的next
console.log('fn1-2')
})
执行这个函数 就会打印出fn1-1
然后就会执行next()
函数,看上上一块代码
,执行next()
函数里面会调用 dispatch(i + 1)
也就是调用第fn = middleware[1]
正是第二个中间件。
看到这里大家就大概明白了。然后进入第二个中间件执行fn
,打印出fn2-1
,继续执行next()
函数,next函数里面继续调用 dispatch(i + 1)
,
也就是fn = middleware[2]
第三中间件函数,打印出fn3-1
,继续执行next()
函数里面会调用 dispatch(i + 1)
,也就是fn = middleware[3]
,
这里注意了,if (i === middleware.length) fn = next
到这里会符合这个条件,然后把next
赋值给fn
这里的next
就是这个fnMiddleware(ctx).then(handleResponse).catch(onerror);
调用时候传入的,然而这里并没有传入,所以这时候 fn
就是 undefined
,然后继续执行到if (!fn) return Promise.resolve()
返回一个空的值,这就是第三个中间件的next
执行结果,
然后继续执行下一行就打印出了fn3-2
,最后向上执行到fn2-2
,然后到fn1-2
, 整个中间件的执行过程。很像洋葱模型,一层层进入,然后一层层出来。
好了整个中间件执行过程就是酱紫啦~~~
最后安利一波博客: https://github.com/naihe138/n...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。