Koa的中间件源码解析
Mercer-Lee的空间 2020-05-09
JS
Koa
Node
# 中间件
Node 的 web 框架要讲最有魅力的设计那肯定就是“中间件”了,无论是 express 还是 koa(egg 是在 koa 的基础上开发的框架),他们的中间件都是一大特色,特别是 koa,无数的 koa 使用者就是看中了 koa 的“洋葱模型中间件”才使用 koa 的,接下来我们就用源码来解释洋葱中间件的原理:
"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);
}
}
};
}
代码很少,甚至于核心代码不过十多行,我在代码上写上注释,基本就能知道怎么实现的了:
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!")
}
return function(context, next) {
// index初始值为-1
let index = -1
// 默认第一个中间件函数执行
return dispatch(0)
function dispatch(i) {
// 如果执行两次next函数的时候报错
if (i <= index) return Promise.reject(new Error("next() called multiple times"))
// 设置当前中间件函数
index = i
let fn = middleware[i]
// 当i是中间件数组最后一个索引的时候,fn为underfind,直接返回一个空值的Promise对象
if (i === middleware.length) fn = next
if (!fn) return Promise.resolve()
// 接下来就是核心代码,返回的就是一个Promise,当走下去没有next()的时候,执行堆栈将会退出,从最后一个的中间件开始恢复上游的行为,所以形成了洋葱模型,不懂堆栈的可以补下关于栈的知识点。
try {
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
} catch (err) {
return Promise.reject(err)
}
}
}
}
本身compose返回的就是一个Promise,然后通过中间件的await next()去控制,next()其实就是下一个中间件函数,所以等待下一个中间件执行完成之后才会执行后面的内容。其实redux-thunk (opens new window)也是借鉴了这种设计,react开发者或者感兴趣的朋友可以去看下。
# 结语
框架或者库重要的不是多么花里胡哨的代码,而是设计思想,只要了解了设计思想,你也能写出那些看起来高大上的技术。