koa2 中间件里面的next到底是什么
Posted 王超元
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了koa2 中间件里面的next到底是什么相关的知识,希望对你有一定的参考价值。
koa2短小精悍,女人不爱男人爱。
之前一只有用koa写一点小程序,自认为还吼吼哈,知道有一天某人问我,你说一下 koa或者express中间件的实现原理。然后我就支支吾吾,好久吃饭都不香。
那么了解next的最好办法是什么, 百度,谷歌,知乎? 没错,肯定有用,我觉得最有用的是看源码和debug去理解。
先看下面的一段代码 ,会输出什么,只会输出 X-Response-Time
const Koa = require(‘koa‘); const app = new Koa(); // x-response-time app.use(async (ctx) => { const start = Date.now(); //await next(); const ms = Date.now() - start; ctx.set(‘X-Response-Time‘, `${ms}ms`); console.log(‘X-Response-Time‘, `${ms}ms`) }); // logger app.use(async (ctx) => { const start = Date.now(); //await next(); const ms = Date.now() - start; console.log(`${ctx.method} ${ctx.url} - ${ms}`); }); // response app.use(async ctx => { console.log(‘Hello World‘) ctx.body = ‘Hello World‘; }); app.listen(3000);
然后修改成如下代码,会依次输出
const Koa = require(‘koa‘); const app = new Koa(); // x-response-time app.use(async (ctx, next) => { const start = Date.now(); await next(); const ms = Date.now() - start; ctx.set(‘X-Response-Time‘, `${ms}ms`); console.log(‘X-Response-Time‘, `${ms}ms`) }); // logger app.use(async (ctx, next) => { const start = Date.now(); await next(); const ms = Date.now() - start; console.log(`${ctx.method} ${ctx.url} - ${ms}`); }); // response app.use(async ctx => { console.log(‘Hello World‘) ctx.body = ‘Hello World‘; }); app.listen(3000);
从上面的结果看来,发现什么没有,没有next 就没有下面的执行,可就简单的一个 await next(), 为嘛会有这种效果,这里,我首先简单说一下koa2中间件的实现原理。
这里先从 koa的使用说起
const Koa = require(‘koa‘); const app = new Koa();
app.use(async (ctx, next) => { const start = Date.now(); await next(); const ms = Date.now() - start; console.log(`${ctx.method} ${ctx.url} - ${ms}`); });
app.listen(3000);
我们顺藤摸瓜,打开 koa里面的application.js (或者直接debug进入),
1.首先看 use ,就是push一个函数到 this.middleware
2. 再看listen, 方法里面 http.createServer(this.callBack), this.callBack返回的是 function(req,res){......}的函数,连起来就是 http.createServer(function(req,res){....}),标准的http创建服务的方法
3. 最后看callback,里面的核心方法, compose(this.middleware) 返回一个promise,处理完毕后再执行 handleResponse
这三个连起来,就是每次请求的时候,先进入callback, compose中间件,执行完毕后,接着处理请求。那剩下的重点变为 compose
use(fn) { if (typeof fn !== ‘function‘) throw new TypeError(‘middleware must be a function!‘); if (isGeneratorFunction(fn)) { deprecate(‘Support for generators will be removed in v3. ‘ + ‘See the documentation for examples of how to convert old middleware ‘ + ‘https://github.com/koajs/koa/blob/master/docs/migration.md‘); fn = convert(fn); } debug(‘use %s‘, fn._name || fn.name || ‘-‘); this.middleware.push(fn); return this; }
listen(...args) { debug(‘listen‘); const server = http.createServer(this.callback()); return server.listen(...args); }
callback() { const fn = compose(this.middleware); if (!this.listeners(‘error‘).length) this.on(‘error‘, this.onerror); const handleRequest = (req, res) => { res.statusCode = 404; const ctx = this.createContext(req, res); const onerror = err => ctx.onerror(err); const handleResponse = () => respond(ctx); onFinished(res, onerror); return fn(ctx).then(handleResponse).catch(onerror); }; return handleRequest; }
我们继续深入研究 compose,看源码,核心依旧是标粗的部分,核心的核心就是dispatch, dispatch会根据 middleware 的长度,依次执行。
‘use strict‘ /** * Expose compositor. */ module.exports = compose /** * Compose `middleware` returning * a fully valid middleware comprised * of all those which are passed. * * @param {Array} middleware * @return {Function} * @api public */ 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 */ return function (context, next) { // last called middleware # 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) } } } }
注意下面,如果 next为空,直接返回,也就出现了我们第一段代码的情况,后面的中间件就game over了。
if (i === middleware.length) fn = next if (!fn) return Promise.resolve()
在往下分析,假定现在执行第一个fn,这个时候第一个fn是什么
return Promise.resolve(fn(context, function next () { return dispatch(i + 1) }))
这时候fn为如下,
fn = async (ctx, next) => { const start = Date.now(); await next(); const ms = Date.now() - start; ctx.set(‘X-Response-Time‘, `${ms}ms`); console.log(‘X-Response-Time‘, `${ms}ms`) }
与上面的参数对应关系如下
context :ctx,
next : function next(){ return dispatch(i+1)}
所以 await next() 就等于 await function next(){ return dispatch(i+1)} , 而 dispatch(i+1)就进入了下一个中间件了。
核心就是 dispatch(i+1),也就是dispatch(1) , dispatch本身返回promise, 所以你就在这里 await 。
依此类推 disptach(1) 会执行 this.middleware[1], 那个时候 fn就为 logger执行的函数,就这么推下去。
关于结束,还是 next 不存在的时候。 结果完毕后,再依次往上走。
所以执行的顺序是越先注册越后执行, 当然还得看你 await next() 放在什么位置。 因为这里我的 console.log都放在了 await的后面,都放到前面,结果如何,亲自测试一下喽。
以上是关于koa2 中间件里面的next到底是什么的主要内容,如果未能解决你的问题,请参考以下文章