【Web技術】904- Express/Koa/Redux三者中間件對比

Author: AddOneG

Link: http://yoursite.com/2018/09/14/express-koa-redux三者中間件對比/


這三者對各自的中間件有着不同的實現,作者本人對此也比較好奇,在這裏小小的研究一下源碼,探究三者之間的異同

什麼是中間件

在我看來,中間件就是在你的代碼運行中進行一些修改的工具。比如你想喝水,那麼喝水之前你將水淨化就可以理解爲是一次中間件的執行。他不是插件,獨立於程序之外,而更像是在你的代碼中表現一種類似連接的功能

Koa 與 Express 中間件概述

這兩者都是Node層面的,這裏我們根據官方文檔來對比

Express

var app = express();

// 沒有掛載路徑的中間件,應用的每個請求都會執行該中間件
app.use(function (req, res, next{
  console.log('Time:'Date.now());
  next();
});

// 掛載至 /user/:id 的中間件,任何指向 /user/:id 的請求都會執行它
app.use('/user/:id'function (req, res, next{
  console.log('Request Type:', req.method);
  next();
});

// 路由和句柄函數(中間件系統),處理指向 /user/:id 的 GET 請求
app.get('/user/:id'function (req, res, next{
  res.send('USER');
});

可以看到express的中間件是使用next進行線性調用的,一個接着一個的執行,是一種尾遞歸的調用(後文會講)。然後在最後一箇中間件中進行對response的處理(習慣)

Koa

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

// 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 => {
  ctx.body = 'Hello World';
});

app.listen(3000);

從代碼中的await可以看出,koa的中間件絕對不是線性的,因爲一旦使用了await,代碼就會停止當前中間件的執行轉而去執行await後面的代碼,這裏next表示下一個中間件。所以這是一個支持generator的洋蔥圈模型(後文會講)

Koa 與 Express 中間件源碼進一步解析

上面提到,express的中間件是尾遞歸調用,而koa的中間件因爲使用了await所以是支持generator的洋蔥圈模型,這裏以此展開來分析代碼

Express

我們直接進入application.js中觀察中間件處理

app.handle = function(req, res, callback{
  var stack = this.stack;
  var idx = 0;
  function next(err{
    if (idx >= stack.length) {
      callback('err'
      return;
    }
    var mid;
    while(idx < stack.length) {
      mid = stack[idx++];
      mid(req, res, next);
    }
  }
  next()
}

這裏next方法不斷取出stack中的中間件並且將自己傳遞給中間件作爲參數,這樣中間件只需要調用next方法就能不斷傳遞到下一個中間件。在函數的末尾遞歸調用了next方法,所以稱爲尾遞歸調用

Koa

Koa對中間件的處理是在一個獨立的包koa-compose中

'use strict'

module.exports = compose

function compose (middleware{

  return function (context, next{
    let index = -1
    return dispatch(0)
    function dispatch (i{
      index = i
      let fn = middleware[i]
      if (i === middleware.length) fn = next
      if (!fn) return Promise.resolve()
      try {
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

Koa中使用了Promise來支持異步,這裏不停調用dispatch.bind(null, i + 1)傳遞下一個中間件,一個一箇中間件向裏執行,直到最後一箇中間件執行完resolve掉,然後不斷向前resolve中間件,直到第一個中間件被resolve。我們可以發現,相應的處理並不在中間件中而是在其resolve後

Redux

對於redux的基礎createStore,reducer,dispatch等就不解釋了,這> 裏直接看applyMiddleware的代碼

import compose from './compose'

export default function applyMiddleware(...middlewares{
  return createStore => (...args) => {
    const store = createStore(...args)
    let dispatch = () => {
      throw new Error(
        `Dispatching while constructing your middleware is not allowed. ` +
          `Other middleware would not be applied to this dispatch.`
      )
    }

    const middlewareAPI = {
      getState: store.getState,
      dispatch(...args) => dispatch(...args)
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

這裏還是比較好理解的,middlewareAPI中包含兩個api,一個是store的getState;另一個是覆寫的dispath,這是一個外部變量,最終指向覆寫後的dispach,對於compose的作用是compose(f, g, h) 返回 () => f(g(h(..args)))

那麼dispatch = compose(...chain)(store.dispatch)即原生的 store.dispatch 傳入最後一個“中間件”,返回一個新的dispatch ``, 再向外傳遞到前一箇中間件,直至返回最終的dispatch`, 當覆寫後的dispatch調用時,每個“中間件“的執行又是從外向內的”洋蔥圈“模型

1. JavaScript 重溫系列(22篇全)
2. ECMAScript 重溫系列(10篇全)
3. JavaScript設計模式 重溫系列(9篇全)
4.  正則 / 框架 / 算法等 重溫系列(16篇全)
5.  Webpack4 入門(上) ||  Webpack4 入門(下)
6.  MobX 入門(上)  ||   MobX 入門(下)
7. 100 +篇原創系列彙總

回覆“加羣”與大佬們一起交流學習~

點擊“閱讀原文”查看 100+ 篇原創文章

本文分享自微信公衆號 - 前端自習課(FE-study)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章