中间件
首先写一个简单的中间件demo:
const Koa = require('koa')
const app = new Koa()
const port = 3000
const ctx1 = async (ctx, next) => {
console.log('开始执行中间件1')
await next()
ctx.response.type = 'text/html'
ctx.response.body = '<h3>hello world</h3>'
console.log('结束执行中间件1')
}
app.use(ctx1)
app.use(async function ctx2 (ctx, next) {
console.log('开始执行中间件2')
await next()
console.log('结束执行中间件2')
})
app.listen(port, () => {
console.log(`server is running on the port: ${port}`)
})
复制代码
很明显中间件执行顺序是这样的:
开始执行中间件1
开始执行中间件2
结束执行中间件2
结束执行中间件1
复制代码
你可以理解为koa2会先按照中间件注册顺序执行next()之前的代码, 执行完到底部之后, 返回往前执行next()之后的代码。
重点是我们需要koa2源码究竟是怎么样执行的? 现在开始调试模式进入koa2源码一探究竟。
- 首先在两个中间件注册的地方打了断点
- 我们可以看到koa2是先按照你中间件的顺序去注册执行
扫描二维码关注公众号,回复:
4805092 查看本文章
- 然后会进入callback. 这是因为
// 应用程序
app.listen(port, () => {
console.log(`server is running on the port: ${port}`)
})
// 源码
listen(...args) {
debug('listen');
const server = http.createServer(this.callback());
return server.listen(...args);
}
复制代码
这个时候this.middleware已经存了两个中间件。
- 这个时候你请求一个路由比如
http://localhost:3000/a
复制代码
koa2的中间件处理就是在这个函数里面
callback() {
// compose()这是处理中间件的执行顺序所在
}
复制代码
于是我们进入这个koa-compose的源码看下:
'use strict'
/**
* Expose compositor.
*/
module.exports = compose
/**
* Compose `middleware` returning
* a fully valid middleware comprised
* of all those which are passed.
*
* @param {Array} middleware
* @return {Function}
* @api public
*/
function compose (middleware) {
// 首先是一些中间件格式校验
if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
for (const fn of middleware) {
if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
}
/**
* @param {Object} context
* @return {Promise}
* @api public
*/
return function (context, next) {
// last called middleware #
let index = -1
// 返回一个函数, 从第一个中间件开始执行, 可以通过next()调用后续中间件
return dispatch(0)
// dispatch始终返回一个Promise对象
function dispatch (i) {
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
let fn = middleware[i]
if (i === middleware.length) fn = next
if (!fn) return Promise.resolve()
try {
// next即就是通过dispatch(i+1)来执行下一个中间件
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
// 捕获中间件中发生的异常
return Promise.reject(err)
}
}
}
}
复制代码
此时i=0取出第一个中间件,由于闭包原因i是一直存在的。
这个时候可以看到fn就是ctx1。
注意
// next即就是通过dispatch(i+1)来执行下一个中间件
dispatch.bind(null, i + 1)
复制代码
这个时候开始进入第一个中间件执行第一句console.log('开始执行中间件1')
这里也能看到next指的就是前面提到的dispatch.bind。
然后我们继续单步调试进入这句
// ctx1中的
await next()
复制代码
此时又重新进入compose(), 继续执行下一个中间件, i=1
取出第二个中间件函数ctx2。
此时进入第二个中间件ctx2开始执行console.log('开始执行中间件2')
继续单步调试
此时i=2,fx=undefined
// 这个洋葱模型的最后做一个兜底的处理
if (!fn) return Promise.resolve()
复制代码
执行中间件ctx2的第二句console