koa
koajs:next generation web framework for node.js。
Koa
是基于Node.js
的下一代web
开发框架。它是基于中间件机制的优雅、简洁、表达力强、自由度高的简单好用的Web
框架。通过将各种独立功能的中间件进行自由组合实现具有特定功能的web
应用。如通过koa-router
中间件实现路由功能、通过koa-cors
实现跨域功能等等。在这些中间件中,有一个中间件比较特殊koa-compose
,也正是这个中间件的使用才使得在koa
中有了我们熟知的洋葱模型。
洋葱模型
洋葱模型是koa
中间件的串行控制流程。
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx, next) => {
console.log('enter first middleware');
await next();
console.log('out first middleware');
});
app.use(async (ctx, next) => {
console.log('enter second middleware');
await next();
console.log('out second middleware');
});
app.use(async (ctx, next) => {
console.log('enter third middleware');
await next();
console.log('out third middleware');
});
app.listen(3000);
输出如下:
如何让中间件以洋葱模型的方式运行,就是koa-compose
的实现的功能。
koa-compose 源码
如上面的代码所示,use
的中间件入参:ctx
和next
,其中ctx
是koa
中的上下文对象,那么next
又是什么呢?带着这个问题我们阅读一下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
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)
}
}
}
}
koa-compose
入参是中间件数组,return
的是一个匿名的中间件函数。
function compose (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!')
}
// 返回一个匿名中间件函数
return funxtion (ctx, next) {}
}
具体看一下return
的这个匿名函数是什么:
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)));
这也就让我们看到了next
函数到底是什么,是dispatch.bind(null, i + 1)
。也正是通过dispatch
将控制权移交给了下一个中间件。在use
中await next()
正式将控制权移交给下一个中间件,第一个 => 第二个 => ... => 最后一个
,当最后一个中间件执行完毕时,此时,开始执行栈将当前栈顶执行环境出栈,最后一个 => 倒数第二个 => ... =>第一个
。也就形成了洋葱模型。