Koa 有两个核心知识点:一个是中间件ctx,一个就是洋葱模型。 中间件ctx利用js原型,很巧妙的把request和response对象封装在里面。 另外一个核心就是本文要分析的洋葱模型。
1 认识洋葱模型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 const Koa = require ('koa' );const app = new Koa();const PORT = 3000 ;app.use(async (ctx, next)=>{ console .log(1 ) await next(); console .log(1 ) }); app.use(async (ctx, next) => { console .log(2 ) await next(); console .log(2 ) }) app.use(async (ctx, next) => { console .log(3 ) }) app.listen(PORT); console .log(`http://localhost:${PORT} ` );
打印顺序
当程序运行到await next()的时候就会暂停当前程序,进入下一个中间件,处理完之后才会回过头来继续处理。
2 原理 核心:中间件管理和next实现,其中next是巧妙的使用了Promise特性。洋葱模型,本质上是Promise.resolve()的递归。
2.1 中间件管理 app.listen使用了this.callback()来生成node的httpServer的回调函数。
1 2 3 4 5 listen(...args) { debug('listen' ); const server = http.createServer(this .callback()); return server .listen(...args); }
Koa.js中间件引擎是有koa-compose模块,即如下的compose方法
1 2 3 4 5 6 7 8 9 10 11 12 callback() { const fn = compose(this .middleware); if (!this .listeners('error' ).length) this .on('error' , this .onerror); const handleRequest = (req, res) => { const ctx = this .createContext(req, res); return this .handleRequest(ctx, fn); }; return handleRequest; }
当我们app.use的时候,只是把方法存在了一个数组里
1 2 3 4 use(fn) { this .middleware.push(fn); return this ; }
2.2 next实现 dispatch函数,它将遍历整个middleware,然后将context和dispatch(i + 1)传给middleware中的方法。 dispatch return Promise这段代码就很巧妙的实现了两点:
将context一路传下去给中间件 将middleware中的下一个中间件fn作为未来next的返回值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 function compose (middleware ) { return function (context, 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 (context, function next ( ) { return dispatch (i + 1 ) })) } catch (err) { return Promise .reject (err) } } } }
3 思考 洋葱模型实现原理,等同于如下代码:next()返回的是promise,需要使用await去等待promise的resolve值。 promise的嵌套就像是洋葱模型的形状就是一层包裹着一层,直到await到最里面一层的promise的resolve值返回。
1 2 3 4 5 6 7 8 9 10 Promise .resolve(middleware1(context, async () => { // 注意async 关键字不能省略 return Promise .resolve(middleware2(context, async () => { return Promise .resolve(middleware3(context, async () => { return Promise .resolve(); })); })); })) .then (() => { console.log('end' ); });