1. 中间件执行顺序
问题1:
const Koa = require("koa");
const app = new Koa();
app.use(async (ctx, next) => {
console.log(1);
await next();
console.log(2);
});
app.use(async (ctx, next) => {
console.log(3);
// await sleep();
await next();
console.log(4);
});
app.use(async (ctx, next) => {
console.log(5);
await next();
console.log(6);
});
app.listen(3000, function () {
console.log("开启成功");
});
// 打印结果 1、3、5、6、4、3
问题2:
const Koa = require("koa");
const app = new Koa();
app.use((ctx, next) => {
console.log(1);
next();
console.log(2);
});
app.use((ctx, next) => {
console.log(3);
next();
console.log(4);
});
app.use((ctx, next) => {
console.log(5);
next();
console.log(6);
});
app.listen(3000, function () {
console.log("开启成功");
});
// 打印结果 1、3、5、6、4、3
问题3:
const Koa = require("koa");
const app = new Koa();
const sleep = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("sleep");
resolve();
}, 1000);
});
};
app.use((ctx, next) => {
console.log(1);
next();
console.log(2);
});
app.use((ctx, next) => {
console.log(3);
await sleep();
next();
console.log(4);
});
app.use((ctx, next) => {
console.log(5);
next();
console.log(6);
});
app.listen(3000, function () {
console.log("开启成功");
});
// 打印结果 1、3、2、sleep、5、6、4
根据执行结果,我可以把整个执行模型解释为洋葱模型
2. koa、koa-compose
中间件注入
koa的中间件主要通过use方法进行注入
class Application {
constructor (){
this.middleware = []
}
use (fn) {
if (typeof fn !== 'function') throw new TypeError('middleware must be a function!')
debug('use %s', fn._name || fn.name || '-')
// 将中间件方法放到middleware上,并返回this方便链式调用
this.middleware.push(fn)
return this
}
}
中间件执行
koa中 执行过程如下
const compose = require('koa-compose')
// listen
class Application {
constructor (options) {
this.middleware = []
this.compose = options.compose || compose
}
// 执行listen
listen (...args) {
const server = http.createServer(this.callback())
return server.listen(...args)
}
callback () {
// 生成中间件
const fn = this.compose(this.middleware)
// 执行这个函数
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res)
if (!this.ctxStorage) {
// 核心执行就是handleRequest
return this.handleRequest(ctx, fn)
}
return this.ctxStorage.run(ctx, async () => {
return await this.handleRequest(ctx, fn)
})
}
return handleRequest
}
handleRequest (ctx, fnMiddleware) {
const res = ctx.res
res.statusCode = 404 // 修改状态码 默认是404
const onerror = err => ctx.onerror(err)
const handleResponse = () => respond(ctx)
onFinished(res, onerror)
// 执行中间件
return fnMiddleware(ctx).then(handleResponse).catch(onerror)
}
}
我们此时可以知道核心方法是compose。在执行listen的时候,生成一个context上下文,并且将上下文传递到中间件里,此时我们可以理解为 通过compose将中间件进行联合,生成context,处理请求,监听server。
const compose = require('koa-compose')
// listen
class Application {
constructor() {
this.middlerware = [];
}
listen(...args) {
const middlerFn = compose(this.midderware);
const server = http.createServer((req,res) => {
const ctx = this.createContext(req, res);
return middlerFn(ctx).then(res => {
// 响应处理
}).catch(err => {
// 错误处理
})
})
server.listen(...args);
}
}
compose 实现原理
/**
* middleware 必须是 函数数组
*/
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
*/
// 这个函数就是中间件实际处理函数
// app.use((ctx, next) => ...); 其中next就是下面的dispatch方法
// 这个dispatch 会调用middleware对应的那个方法
// next 上文中middlerFn的第二个参数,也就是说执行完middleware之后可以执行自定义逻辑
return function (context, next) {
// 默认 index是 -1 表示未执行的状态
let index = -1
// 执行第一个middlerwaree
return dispatch(0)
function dispatch (i) {
// 出现index = i的场景 某一个中间件里调用了两次next函数
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
// 赋值index;
index = i
// 找到middleware方法
let fn = middleware[i]
// 执行完middleware了,看一下next有没有传,如果有传在走一遍逻辑
if (i === middleware.length) fn = next
// 如果没有middleware 返回一个空的promise
if (!fn) return Promise.resolve()
try {
// 依次向下调用
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
} catch (err) {
return Promise.reject(err)
}
}
}
}
此时可以知道koa作者借助promise的特性实现洋葱模型
// 简单模拟
async function p1 (next) {
console.log(1);
await next(); // 此时的next就是p2
console.log(2)
}
async function p2 (next) {
console.log(3);
await next(); // 此时的next就是p3
console.log(4)
}
async function p3 (next) {
console.log(5);
await next();
console.log(6)
}
const list = [p1,p2,p3];
function compose(list) {
let index = -1;
return () => {
function next(i) {
if (index >= i) throw new Error('调用了多次next');
index = i;
const fn = list[i];
if(!fn) {
return Promise.resolve();
}
try {
return Promise.resolve(fn(next.bind(null, i+1)));
} catch (err) {
return Promise.reject(err);
}
}
return next(0);
}
}
const fn = compose(list);
fn();