koa2 中間件裏面的next究竟是什麼

koa2短小精悍,女人不愛男人愛。git

以前一隻有用koa寫一點小程序,自認爲還吼吼哈,知道有一天某人問我,你說一下 koa或者express中間件的實現原理。而後我就支支吾吾,很久吃飯都不香。github

那麼瞭解next的最好辦法是什麼, 百度,谷歌,知乎?  沒錯,確定有用,我以爲最有用的是看源碼和debug去理解。express

 

先看下面的一段代碼 ,會輸出什麼,只會輸出  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);
複製代碼

而後修改爲以下代碼,會依次輸出api

Hello World
GET / - 8
X-Response-Time 1040ms
複製代碼
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中間件的實現原理。promise

這裏先從 koa的使用提及app

複製代碼
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進入),koa

1.首先看 use ,就是push一個函數到 this.middlewareasync

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的後面,都放到前面,結果如何,親自測試一下嘍。 

相關文章
相關標籤/搜索