- 本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。
1. Koa 中间件
Koa 的中间件通过一种更加传统的方式进行级联,摒弃了以往 node 频繁的回调函数造成的复杂代码逻辑。使用异步函数,我们可以实现"真正" 的中间件。与之不同,当执行到 yield next 语句时,Koa 暂停了该中间件,继续执行下一个符合请求的中间件('downstrem'),然后控制权再逐级返回给上层中间件('upstream')。(koa中文文档的描述)
在koa的文档上有个非常代表性的中间件执行 gif 图。
2. Koa Compose 源码解析
2.1 test.js测试compose的代码,先对compose的使用有个了解
//test.js
function wait(ms) {
return new Promise((resolve) => setTimeout(resolve, ms || 1));
}
function isPromise(x) {
return x && typeof x.then === "function";
}
describe("Koa Compose", function () {
it.only("should work", async () => {
const arr = [];
const stack = [];
//这里删除了部分测试代码
stack.push(async (context, next) => {
arr.push(1);
await wait(1);
await next(); //dispatch(1)
await wait(1);
arr.push(6);
});
stack.push(async (context, next) => {
arr.push(2);
await wait(1);
await next(); //dispatch(2)
await wait(1);
arr.push(5);
});
//调用compose方法,传入异步任务队列,执行compose返回
await compose(stack)({});
expect(arr).toEqual(expect.arrayContaining([1, 2, 5, 6]));
});
});
2.2. compose源码解析
function compose(middleware) {
if (!Array.isArray(middleware))
// 判断middleware是否是数组,判断数组的方法能想到几种呢
throw new TypeError("Middleware stack must be an array!");
for (const fn of middleware) {
// 判断middleware里面的数据是否是函数, 顺便了解下for of 语法
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)
//如果i==index说明,同一个dispatch(fn里面的next)调用了多次,抛出错误
return Promise.reject(new Error("next() called multiple times"));
index = i;
let fn = middleware[i];
if (i === middleware.length) {
// compose(stack)({}),没有传递第二个参数,所以next=undefined
fn = next;
}
if (!fn) {
return Promise.resolve();
}
try {
//这里取middleware的函数去执行,函数内部执行next(),执行middleware的下一个函数
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err);
}
}
};
}
/*
测试脚本的调用流程
dispatch(0)
调用stack[0]的方法, 内部next()调用的是 dispach(1)
arr=[1]
dispach(1)
调用stack[1], next= dispatch(2)
arr=[1,2]
当2==middleware.length ,compose(stack)({}),没有传递第二个参数,所以next=undefined
后面的就是顺序执行
await wait(1);
arr.push(5);
await wait(1);
arr.push(6);
*/
2.3. vsCode中debug方法截图
在package.json里找的script,有有Debug的按钮,点击选择执行test
扩展: redux中compose的代码
function compose(...funcs: Function[]) {
if (funcs.length === 0) {
// infer the argument type so it is usable in inference down the line
return <T>(arg: T) => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce(
(a, b) =>
(...args: any) =>
a(b(...args))
)
}
3.小结
koa-compose源码比较少,调试了好几遍才理解执行流程和原理。其中包含着闭包、Promise、bind等基础知识。
熟悉了 koa-compose 中间件常说的洋葱模型,学会了部分 jest 用法,如果单独执行某一个单元测试,可以使用it.only()。
it.only('should work', async () => {
//xxxx
})