先来看一个 demo
const Koa = require('koa');
const app = new Koa();
// 中间件1
app.use((ctx, next) => {
console.log("<<<1");
next();
console.log("1>>>");
});
// 中间件 2
app.use((ctx, next) => {
console.log("<<<2");
next();
console.log("2>>>");
});
// 中间件 3
app.use((ctx, next) => {
console.log("<<<3");
next();
console.log("3>>>");
});
app.listen(8000, '0.0.0.0', () => {
console.log(`Server is starting`);
});
输出的结果是:
<<<1
<<<2
<<<3
3>>>
2>>>
1>>>
在 koa 中,中间件被 next() 方法分成了两部分。next() 方法上面部分会先执行,下面部门会在后续中间件执行全部结束之后再执行。可以通过下图直观看出:
在洋葱模型中,每一层相当于一个中间件,用来处理特定的功能,比如错误处理、Session 处理等等。其处理顺序先是 next() 前请求(Request,从外层到内层)然后执行 next() 函数,最后是 next() 后响应(Response,从内层到外层),也就是说每一个中间件都有两次处理时机。
我们以文章开始时候的 demo 来分析一下 koa 内部的实现。
const Koa = require('koa');
const app = new Koa();
// 中间件1
app.use((ctx, next) => {
console.log("<<<1");
next();
console.log("1>>>");
});
// 中间件 2
app.use((ctx, next) => {
console.log("<<<2");
next();
console.log("2>>>");
});
// 中间件 3
app.use((ctx, next) => {
console.log("<<<3");
next();
console.log("3>>>");
});
app.listen(8000, '0.0.0.0', () => {
console.log(`Server is starting`);
});
use 方法就是做了一件事,将用户自定义的函数模块收集到 middleware 中间件数组里
use(fn) {
this.middleware.push(fn);
}
执行 app.listen 方法的时候,其实是 Node.js 原生 http 模块 createServer 方法创建了一个服务,其回调为 callback 方法。
listen(...args) {
//请求进来后,我们在这里处理所有的中间件函数
const server = http.createServer(this.callback());
server.listen(...args);
}
callback方法就会将use方法存储的middleware中间件数组传给compose函数,callback方法方法内部借用了compose函数实现了对所有中间件函数的调用。
一个简易版的callback方法
callback() {
//向 compose传递中间件函数数组
const fnMiddleWare = this.compose(this.middleware);
//调用compose返回的方法,实现对所有中间件的调用
const handleRequest = (req, res) => {
fnMiddleWare()
.then(() => {
console.log("end");
res.end("my koa");
})
.catch((err) => {
res.end(err.message);
console.log("err", err);
});
};
return handleRequest;
}
compose方法是洋葱模型的核心,其返回一个函数
compose(middleware) {
return function () {
return xxxxx;
};
}
因此,在callback函数中,我们用变量进行了接收并调用
callback() {
const fnMiddleWare = this.compose(this.middleware);
fnMiddleWare()
.....
return xxxx;
}
compose返回的函数体内,定义了一个dispath方法,我们在调用compose返回的函数时,实际在调用dispath这个方法
compose(middleware) {
return function () {
const dispatch = (index) => {
......
};
return dispatch(0);
};
}
现在,我们来看看 dispatch的主要逻辑
// 异步递归遍历调用中间件处理函数
compose(middleware) {
return function () {
const dispatch = (index) => {
if (index >= middleware.length) return Promise.resolve();
const fn = middleware[index];
return Promise.resolve(
fn({}, () => dispatch(index + 1)));
};
return dispatch(0);
};
}
当我们第一次执行fnMiddleWare()函数时,可以看出,实际执行的是dispatch(0)这个函数。
dispatch( 0 )运行时
const dispatch = (index:0) => {
if (index:0 >= middleware.length:3) return Promise.resolve();
const fn = middleware[index:0];
return Promise.resolve(
fn({}, () => dispatch(index:0 + 1)));
};
我们逐步分析:
//如果index 大于 中件间函数的数量,返回一个成功状态的PROMISE
if (index:0 >= middleware.length:3) return Promise.resolve();
const fn = middleware[index:0];
此时,fn为中间件的第一个回到函数
(ctx, next) => {
console.log("<<<1");
next();
console.log("1>>>");
}
return Promise.resolve(
fn({}, () => dispatch(index:0 + 1))
);
最后一步,返回了一个Promise,因此,在callback函数内,我们需要使用。.then的方式来接收这个最终返回结果。我们中间件的所有执行逻辑,其实都在Promise.resolve里。
当调用 fn({ }, () => dispatch(index:0 + 1) ) 时
实际执行了这个匿名函数
(ctx, next) => {
console.log("<<<1");
next();
console.log("1>>>");
}
ctx此时的值是{ } ,这里的next的值是 () => dispatch(index:0 + 1) 匿名函数
当这个匿名函数运行时,console.log("<<<1")正常执行,我们的浏览器会打印出
<<<1
当执行到这个next() 时,() => dispatch(index:0 + 1) 匿名函数执行,触发
dispatch(1)
此时,第一个fn函数终止运行,开始执行 dispatch(1) ,因此console.log(" 1>>> ")暂时不执行
dispatch(1) 运行时
const dispatch = (index:1) => {
if (index:1 >= middleware.length:3) return Promise.resolve();
const fn = middleware[index:1];
return Promise.resolve(
fn({}, () => dispatch(index:1 + 1)));
};
同理,此时会执行middleware中间函数数组的第二个匿名回调函数
(ctx, next) => {
console.log("<<<2");
next();
console.log("2>>>");
}
同样,先执行 console.log("<<<2") ,紧接着触发
dispatch(2)
console.log("2>>>") 等待执行
dispatch(2) 运行时
const dispatch = (index:2) => {
if (index:2 >= middleware.length:3) return Promise.resolve();
const fn = middleware[index:2];
return Promise.resolve(
fn({}, () => dispatch(index:2 + 1)));
};
同理,此时会执行middleware中间函数数组的第3个匿名回调函数
(ctx, next) => {
console.log("<<<3");
next();
console.log("3>>>");
}
同样,先执行 console.log("<<<3") ,紧接着触发
dispatch(3)
dispatch(3) 运行时
const dispatch = (index:2) => {
if (index:3 >= middleware.length:3) return Promise.resolve();
const fn = middleware[index:2];
return Promise.resolve(
fn({}, () => dispatch(index:2 + 1)));
};
其运行逻辑如下