一、洋葱模型理解
Koa的洋葱模型是以next()函数为分割点,先由外到内执行Request的逻辑,然后再由内到外执行Response的逻辑。使用compose()实现一个dispatch递归,将后一个中间件函数当作参数传入前一个中间件函数,从而实现洋葱模型。
graph TB
a("use()收集中间件")
b("listen()创建http服务")
b1("chllback()创建了ctx并调用compose生成中间件函数")
b2("handleRequest()运行中间件函数并处理成功失败回调")
c("递归中间件函数,执行前一个中间件并向其传入后一个中间件函数")
subgraph compose
c
end
subgraph listen
b --> b1 --> b2
end
subgraph use
a
end
二、什么是洋葱模型
const Koa = require('koa');
const app = new Koa();
// 中间件1
app.use((ctx, next) => {
console.log(1);
next();
console.log(2);
});
// 中间件 2
app.use((ctx, next) => {
console.log(3);
next();
console.log(4);
});
app.listen(8000, '0.0.0.0', () => {
console.log(`Server is starting`);
});
输出的结果是:
1
3
4
2
next() 方法上面部分会先执行,下面部门会在后续中间件执行全部结束之后再执行。
也就是说每一个中间件都有两次处理时机。
三、深入理解洋葱模型
1、use()
use 方法就是得到 middleware 中间件数组
use(fn) {
this.middleware.push(fn);
return this;
}
2、listen()和callback()创建http服务
listen()就是使用http创建一个服务,其中chllback()就是http服务的请求处理函数。
callback()是创建了ctx上下文环境,以及调用compose()函数生成一个中间件函数,然后调用handleRequest()
handleRequest()函数主要是执行中间件函数并处理结果。
listen(...args) {
// node http 创建一个服务
const server = http.createServer(this.callback());
return server.listen(...args);
}
callback() {
// 返回值是一个函数
const fn = compose(this.middleware);
const handleRequest = (req, res) => {
// 创建 ctx 上下文环境
// const ctx = this.createContext(req, res);
const ctx = {
req,
res
};
return this.handleRequest(ctx, fn);
};
return handleRequest;
}
handleRequest(ctx, fnMiddleware) {
const res = ctx.res;
res.statusCode = 404;
const onerror = (err) => ctx.onerror(err);
// const handleResponse = () => respond(ctx);
// onFinished(res, onerror);
// 执行 compose 中返回的函数,将结果返回
return fnMiddleware(ctx)
.then((res) => {
console.log("res:", res);
})
.catch(onerror);
}
3、compose()生成中间件函数
compose()中有一个dispatch()函数进行递归,目的是执行前一个中间件并向其传入后一个中间件函数,所以在use()收集的中间件函数调用next()方法会执行下一个中间件。
compose()是使用了koa-compose库,基本逻辑如下。
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];
if (i === middleware.length) fn = next;
// 当 fn 为空的时候,就会开始执行 next() 后面部分的代码
if (!fn) return Promise.resolve();
try {
// 执行中间件,留意这两个参数,都是中间件的传参,第一个是上下文,第二个是 next 函数
// 也就是说执行 next 的时候也就是调用 dispatch 函数的时候
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err);
}
}
};
}