在使用Koa.js过程中,会发现中间件的使用都是这样子的,如以下代码所示。
const Koa = require('koa');let app = new Koa();const middleware1 = async (ctx, next) => {console.log(1);await next();console.log(6);}const middleware2 = async (ctx, next) => {console.log(2);await next();console.log(5);}const middleware3 = async (ctx, next) => {console.log(3);await next();console.log(4);}app.use(middleware1);app.use(middleware2);app.use(middleware3);app.use(async(ctx, next) => {ctx.body = 'hello world'})app.listen(3001)// 启动访问浏览器// 控制台会出现以下结果// 1// 2// 3// 4// 5// 6
为什么会出现以上的结果, 这个主要是Koa.js的一个中间件引擎 koa-compose
模块来实现的,也就是Koa.js实现洋葱模型
的核心引擎。
洋葱模型可以看出,中间件的在 await next()
前后的操作,很像数据结构的一种场景——“栈”,先进后出。同时,又有统一上下文管理操作数据。综上所述,可以总结出一下特性。
context
next
这样子我们可以单纯用 Promise
做个简单的实现如下
let context = {data: []};async function middleware1(ctx, next) {console.log('action 001');ctx.data.push(1);await next();console.log('action 006');ctx.data.push(6);}async function middleware2(ctx, next) {console.log('action 002');ctx.data.push(2);await next();console.log('action 005');ctx.data.push(5);}async function middleware3(ctx, next) {console.log('action 003');ctx.data.push(3);await next();console.log('action 004');ctx.data.push(4);}Promise.resolve(middleware1(context, async() => {return Promise.resolve(middleware2(context, async() => {return Promise.resolve(middleware3(context, async() => {return Promise.resolve();}));}));})).then(() => {console.log('end');console.log('context = ', context);});// 结果显示// "action 001"// "action 002"// "action 003"// "action 004"// "action 005"// "action 006"// "end"// "context = { data: [1, 2, 3, 4, 5, 6]}"
通过上一节中的中间件原理,可以看出,单纯用Promise
嵌套可以直接实现中间件流程。虽然可以实现,但是Promise
嵌套会产生代码的可读性和可维护性的问题,也带来了中间件扩展问题。
所以需要把Promise
嵌套实现的中间件方式进行高度抽象,达到可以自定义中间件的层数。这时候需要借助前面几章提到的处理 Promise
嵌套的神器async/await
。
我们先理清楚需要的步骤
context
传进去next
根据上一节分析中间的原理,我们可以抽象出
Promise
Promise.resolve
的前后操作源码地址
https://github.com/chenshenhai/koajs-design-note/tree/master/demo/chapter-01-05
function compose(middleware) {if (!Array.isArray(middleware)) {throw new TypeError('Middleware stack must be an array!');}return function(ctx, next) {let index = -1;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;}if (!fn) {return Promise.resolve();}try {return Promise.resolve(fn(ctx, () => {return dispatch(i + 1);}));} catch (err) {return Promise.reject(err);}}};}
试用中间件引擎
let middleware = [];let context = {data: []};middleware.push(async(ctx, next) => {console.log('action 001');ctx.data.push(2);await next();console.log('action 006');ctx.data.push(5);});middleware.push(async(ctx, next) => {console.log('action 002');ctx.data.push(2);await next();console.log('action 005');ctx.data.push(5);});middleware.push(async(ctx, next) => {console.log('action 003');ctx.data.push(2);await next();console.log('action 004');ctx.data.push(5);});const fn = compose(middleware);fn(context).then(() => {console.log('end');console.log('context = ', context);});// 结果显示// "action 001"// "action 002"// "action 003"// "action 004"// "action 005"// "action 006"// "end"// "context = { data: [1, 2, 3, 4, 5, 6]}"