koa-compose 源码分析-洋葱模型串联中间件
前言
先来看下洋葱中间件机制,这种灵活的中间件机制也让 koa 变得非常强大。

源码
核心实现
可以看到只有短短的几十行,本质上就是一个嵌套的高阶函数,外层的中间件嵌套着内层的中间件。利用递归的机制一层嵌套一层,调用 next 之前是 递(req),之后是 归(res)。
function compose(middleware) {
return function (context, next) {
// last called middleware #
let index = -1;
// 从下标为 0 开始执行中间件。
return dispatch(0);
function dispatch(i) {
if (i <= index) return Promise.reject(new Error("next() called multiple times"));
index = i;
// 找出数组中存放的相应的中间件
let fn = middleware[i];
// 不存在返回,最后一个中间件调用 next 也不会报错。
if (i === middleware.length) fn = next;
if (!fn) return Promise.resolve();
try {
return Promise.resolve(
// 执行当前中间件
fn(
// 第一个参数是 ctx。
context,
// 第二个参数是 next,代表下一个中间件。
dispatch.bind(null, i + 1)
)
);
} catch (err) {
return Promise.reject(err);
}
}
};
}
Koa 示例
简单写个示例看它是如何在 Koa 中应用的。
首先通过 use 收集了所有的中间件,在执行的时候当前中间的 next 参数是下一个中间件,那么执行 next 自然就进入了下一个中间件。
其次把这种调用行为看做递归行为,当我们达到终点的时候(最后一个),发生回溯行为直到最初的调用。
const compose = require("../index");
class App {
middlewares = [];
use(fn) {
this.middlewares.push(fn);
}
run() {
compose(this.middlewares)();
}
}
const app = new App();
// 收集中间件
app.use(async (ctx, next) => {
console.log(1);
await next();
console.log(6);
});
app.use(async (ctx, next) => {
console.log(2);
await next();
console.log(5);
});
app.use(async (ctx, next) => {
console.log(3);
await next();
console.log(4);
});
// 执行中间件
app.run(); // 1->2->3->4->5->6
VueRouter 示例
通过上面我们会发现调用 next 尤为重要,那么还在那见过 next 参数呢?
如果知道 VueRouter 的使用,那么在导航守卫中有 next 如果定义了未调用是不会进入下一个路由的。
其实在 VueRouter 并不是使用递归去实现的,而是巧妙的利用了 Promise 链。
如下有个简单的实现,把守卫函数都包装成 Promise,并且定义 next 函数,只有调用 next 函数才会执行 resolve(),然后使用 reduce 依次追加上 promise.then 实现串联。
class VueRouter {
guards = [];
beforeEach(guard) {
this.guards.push(guardToPromiseFn(guard));
}
run() {
runGuardQueue(this.guards);
}
}
const router = new VueRouter();
router.beforeEach((to, from, next) => {
console.log(1);
next();
});
router.beforeEach((to, from) => {
console.log(2);
});
router.run(); // 1 -> 2
// 串行执行守卫
function runGuardQueue(guards) {
// Promise.resolve().then(() => guard1()).then(() => guard2())
// guard() 执行后返回的 Promise
return guards.reduce((promise, guard) => promise.then(() => guard()), Promise.resolve());
}
// 把守卫包装成 Promise
function guardToPromiseFn(guard, to, from) {
return () => {
return new Promise((resolve, reject) => {
// 定义 next ,当执行 next 的时候这个 Promise 才会从 pending -> resolve
const next = () => resolve();
// 执行守卫函数并把 next 函数传递过去
guard(to, from, next);
// 如果守卫函数没有定义 next,默认执行 next
if (guard.length < 3) next();
});
};
}
总结
复习一遍,简单实现两个示例。
