express, koa, redux三者中間件對比

Author: AddOneG前端

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


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

什麼是中間件

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

Koa 與 Express 中間件概述

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

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的洋蔥圈模型(後文會講)app

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

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

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方法,因此稱爲尾遞歸調用async

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.看到這裏了就點個在看支持下吧,你的「點贊,在看」是我創做的動力。

  • 2.關注公衆號前端壹棧,回覆「1」加入前端交流羣!「在這裏有好多前端開發者,會討論前端知識,互相學習」!

  • 3.也可添加公衆號【前端壹棧】,一塊兒成長


本文分享自微信公衆號 - 前端壹棧(Ecmscript)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索