koa
-基于 Node.js
的下一代 web 开发框架。css
它最大的特色就是独特的中间件流程控制,是一个典型的洋葱模型。koa 和 koa2 中间件的思路是同样的,可是实现方式有所区别,koa2 在 Node7.6 以后更是能够直接用 async/await
来替代 generator
使用中间件,本文以最后一种状况举例。node
本文主要是对 compose 模块的源码解读git
下面的图是网上找的,很清晰的代表了一个请求是如何通过中间件最后生成响应的,这种模式中开发和使用中间件都是很是方便的。 咱们都知道在函数式编程的思想中,compose 是将多个函数合并成一个函数(g() + h() => g(h())),koa 中的 compose 则是将 koa/koa-router
各个中间件合并执行,结合 next()
就造成了下图所示的洋葱模型github
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx, next) => {
console.log('第一个中间件函数')
await next();
console.log('第一个中间件函数next以后!');
})
app.use(async (ctx, next) => {
console.log('第二个中间件函数')
await next();
console.log('第二个中间件函数next以后!');
})
app.use(async ctx => {
ctx.body = 'Hello World';
});
app.listen(3000);
复制代码
执行命令 node demo1.js
web
执行结果以下所示:编程
为何会是上面这种结果呢,咱们带着这些疑问一块儿去继续往下看,看到最后确定会能理解。api
注意:在使用app.use
将给定的中间件添加到应用程序时,middlewar
(其实就是一个函数)接收两个参数:ctx
和next
。其中next
也是一个函数。数组
compose 代码以下,去掉注释,代码就 25 行,细读确实是很精妙的代码,虽然看着很短,但粗看几层 return
,仍是会有点绕。bash
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) {
//传入的 middleware 参数必须是数组
if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
//middleware 数组的元素必须是函数
for (const fn of middleware) {
if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
}
/**
* @param {Object} context
* @return {Promise} 返回一个闭包函数,函数的返回是一个Promise 对象, 保持对 middleware 的引用。
* @api public
*/
return function (context, next) {
// last called middleware #
let index = -1
return dispatch(0)
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 {
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
}
}
复制代码
咱们首先去掉条件判断,看下最里面的实际返回闭包
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
复制代码
fn = middleware[i]
也就是某一个中间件,很显然上述代码遍历中间件数组middleware
,依次拿到中间件fn,并执行:
fn(context, dispatch.bind(null, i + 1))
复制代码
这里能够看到传递给中间件的两个参数:context
和next
函数。 前面咱们提到过:在使用app.use
将定的中间件添加到应用程序时,中间件(其实就是一个函数)接收两个参数:ctx
和next
。其中next
也是一个函数。 看到这里是否是清楚了在注册 middwleare
的时候为何要有两个参数了吧~
回到前面的问题,为何咱们的demo
执行的结果会是上面,咱们看第一个中间件,
app.use(async (ctx, next) => {
console.log('第一个中间件函数')
await next();
console.log('第一个中间件函数next以后!');
})
复制代码
带入到代码中,第一次执行 return dispatch(0)
, 这时第一个中间件被调用,继续展开
dispatch(0)
展开Promise.resolve((async (ctx, next) => {
console.log('第一个中间件函数')
await next();
console.log('第一个中间件函数next以后');
})(context, dispatch.bind(null, i + 1)));
复制代码
首先执行 console.log('第一个中间件函数')
没啥毛病, 接下来执行 next()
方法,就跑到第二个中间件去了,因此没有执行第二个 console.log()
app.use(async (ctx, next) => {
console.log('第二个中间件函数')
await next();
console.log('第二个中间件函数next以后!');
})
复制代码
dispatch(1)
展开Promise.resolve(async (ctx, next) => Promise.resolve(async (ctx, next) => {
console.log('第一个中间件函数')
Promise.resolve((async (ctx, next) => {
console.log('第二个中间件函数')
await next();
console.log('第二个中间件函数next以后');
})(context, dispatch.bind(null, i + 1)));
console.log('第一个中间件函数next以后')
}))
复制代码
因此执行 onsole.log('第二个中间件函数')
是否是就很清楚的看出来了。
在第二个中间件执行到await next()
时,一样会轮转到第三个中间件,接下若是有第四个中间件,第五个中间件,聪明的大家会发现,以此类推,直到最后一个中间件。
看到这里,咱们会不会很好奇 koa
是怎么调用compose
的呢,等后面的文章再更新~
以上就是我关于 koa compose 的解读和洋葱模型的解析。但愿对你们有所帮助,从代码上咱们能够看出,洋葱模型也是有所缺陷的,一旦中间件过多,性能仍是会有必定的影响的,因此咱们须要结合本身的项目场景做出合适的选择。
若是以上有问题,欢迎你们留言,一块儿探讨,谢谢!。