博客
关于我
koa2 中间件里面的next到底是什么
阅读量:407 次
发布时间:2019-03-06

本文共 6117 字,大约阅读时间需要 20 分钟。

koa2短小精悍,女人不爱男人爱。

之前一只有用koa写一点小程序,自认为还吼吼哈,知道有一天某人问我,你说一下 koa或者express中间件的实现原理。然后我就支支吾吾,好久吃饭都不香。

那么了解next的最好办法是什么, 百度,谷歌,知乎?  没错,肯定有用,我觉得最有用的是看源码和debug去理解。

 

先看下面的一段代码 ,会输出什么,只会输出  X-Response-Time

const Koa = require('koa');const app = new Koa();// x-response-timeapp.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`)});// loggerapp.use(async (ctx) => {  const start = Date.now();  //await next();  const ms = Date.now() - start;  console.log(`${ctx.method} ${ctx.url} - ${ms}`);});// responseapp.use(async ctx => {  console.log('Hello World')  ctx.body = 'Hello World';});app.listen(3000);

然后修改成如下代码,会依次输出

Hello World
GET / - 8
X-Response-Time 1040ms
const Koa = require('koa');const app = new Koa();// x-response-timeapp.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`)});// loggerapp.use(async (ctx, next) => {  const start = Date.now();  await next();  const ms = Date.now() - start;  console.log(`${ctx.method} ${ctx.url} - ${ms}`);});// responseapp.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;  }

我们继续深入研究 看源码,核心依旧是标粗的部分,核心的核心就是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的后面,都放到前面,结果如何,亲自测试一下喽。 

 

最后简单的模拟一下 Promise.resolve(fn()), 

1.  fn为一个异步函数,所以里面可以await

2.  fn最后返回的是一个Promise对象

3. 当Promise.then, Promise.resolve返回是一个Promise对象时,会执行该Promise对象,并进入下一个环节

4 . 所以p1, p2依次执行,最后结果为6

var p1 = function () {    return new Promise((resolve, reject) => {        setTimeout(function () {            console.log('p1', new Date().toLocaleString())            resolve(1)        }, 2000)    })}var p2 = function () {    return new Promise((resolve, reject) => {        setTimeout(function () {            console.log('p2', new Date().toLocaleString())            resolve(6)        }, 4000)    })}console.log('start', new Date().toLocaleString())Promise.resolve(fn()).then(r => {    console.log('end', new Date().toLocaleString())    console.log(r)})async function fn() {    let a = await p1()    let b = 4    return p2()}// start 2018/3/15 下午8:16:37   // p1 2018/3/15 下午8:16:39  // p2 2018/3/15 下午8:16:43  // end 2018/3/15 下午8:16:43  // 6

  

转载地址:http://bbhkz.baihongyu.com/

你可能感兴趣的文章
技术美术面试问题整理
查看>>
ORB-SLAM2:LoopClosing线程学习随笔【李哈哈:看看总有收获篇】
查看>>
Codeforces Round #305 (Div. 1) B. Mike and Feet(单调栈)
查看>>
js求阶乘
查看>>
Making the grade 和Sonya and Problem Wihtout a Legend
查看>>
Nginx---惊群
查看>>
项目中常用的审计类型概述
查看>>
(九)实现页面底部购物车的样式
查看>>
python-day3 for语句完整使用
查看>>
ButterKnife使用问题
查看>>
为什么讨厌所谓仿生AI的说法
查看>>
ORACLE 客户端工具
查看>>
基于LabVIEW的入门指南
查看>>
weblogic之cve-2015-4852
查看>>
Java注释
查看>>
C++ 函数重载
查看>>
使用mybatis-generator生成底层
查看>>
Mybatis【5】-- Mybatis多种增删改查那些你会了么?
查看>>
计算输入的一句英文语句中单词数
查看>>
lvs+keepalive构建高可用集群
查看>>