之前面试腾讯时,面试官问了我一个问题,“你知道koa的洋葱模型吗?”
我愣了一下,“不知道,没了解过”
虽然我面试的是前端实习,但是面试官从头到尾都在问我后端的问题,我不是很能理解,但是既然被问了这个问题,那么我们就必须在下一次被问的时候能和面试官说道说道
所以我去阅读了koa的源码,然后写下了这篇文章
这篇文章主要讲的是洋葱模型的原理,其他未涉及,我默认读者使用过koa
洋葱
先看下面这段代码,我创建了一个app,然后添加了三个中间件
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx, next)=>{
console.log(1)
await next();
console.log(1)
});
app.use(async (ctx, next) => {
console.log(2)
await next();
console.log(2)
})
app.use(async (ctx, next) => {
console.log(3)
})
app.listen(3000);
通过访问localhost:3000之后,控制台的输出是这样的
1
2
3
2
1
这个现象可以用下面这张图来描述
让我给他加上外壳
有没有觉得很像一颗洋葱!?
是的,这个就是koa的洋葱模型
他的执行过程如图中所展现的那样,从外面一层一层的进去,再一层一层的从里面出来,这个也是koa中间件的执行过程,相信看到这里,你应该已经知道了koa中间件的执行过程,下面是洋葱模型的实现代码
原理
在koa的源码中,中间件们被存储在一个数组中
let middleware = [];
我们可以通过use(...)添加中间件,主要逻辑大概这样
const use = (fn)=>{
middleware.push(fn)
}
而调用中间件的是compose这个函数,下面的代码我做了详细的解释
function compose (middleware) {
return function (context, next) {
// index的作用:记录当前的已经执行过的中间件个数
let index = -1
return dispatch(0)
// i的作用:当前要执行的中间件的下标
function dispatch (i) {
// 如果i<=index 意味着 这是第二遍执行next函数,抛出异常
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
// 更新index,对应上面
index = i
// 提取出当前需要执行的中间件
let fn = middleware[i]
// 执行compose的回调函数
if (i === middleware.length) fn = next
// 不存在则意味着到了最后一个中间件,直接返回
if (!fn) return Promise.resolve()
// 此处try的作用,是保证出现异常时可以传递异常并返回
try {
// 将dispatch传递下去,作为中间件的next函数,形成递归调用
// 可以发现此处i+1了,即调用的是下一个中间件
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
} catch (err) {
return Promise.reject(err)
}
}
}
}