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

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

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

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

 

先看下面的一段代碼 ,會輸出什麼,只會輸出  X-Response-Timegithub

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);

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

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中間件的實現原理。小程序

這裏先從 koa的使用提及api

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進入),promise

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

2. 再看listen, 方法裏面 http.createServer(this.callBack), this.callBack返回的是 function(req,res){......}的函數,連起來就是 http.createServer(function(req,res){....}),標準的http建立服務的方法
koa

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

 

最後簡單的模擬一下 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
相關文章
相關標籤/搜索