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 里面的 next 是什么, 就是 dispatch(i+1), 也就是 dispatch(1) , dispatch 本身返回 promise, 所以你就在这里 await 。
依此类推 disptach(1) 会执行 this.middleware[1], 那个时候 fn 就为 logger 执行的函数,就这么推下去。
关于结束,还是 next 不存在的时候。 结果完毕后,再依次往上走。
所以执行的顺序是越先注册越后执行, 当然还得看你 await next() 放在什么位置。 因为这里我的 console.log 都放在了 await 的后面,都放到前面,结果如何,亲自测试一下喽。
- 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`)
- }
来源: http://www.cnblogs.com/cloud-/p/7239819.html