Compose應用

在用 PHP 開發本身博客後臺的時候,碰到一個問題:通常的後臺框架都會提供中間件,給使用者自定義額外的功能,so,PHP 應該如何整合一系列的功能中間件呢?html

你可能想到的實現

註冊中間件的時候將其存到數組中,執行時遍歷數組。express

const app = new App();

// 註冊中間件
// this.middlewares.push(middleware);
app.use(middleware);

// 使用中間件
// 內部方法實現
function __internal() {
  // some injections here.

  for (let i = 0; i < this.middlewares.length; i++) {
    const fn = this.middlewares[i];

    // 你能夠往中間件注入一些東西
    fn(injection);
  }
}
複製代碼

最簡單、最笨的一種方式。來看看其餘大佬怎麼作的。編程

redux 中間件

應用了函數式編程盛行的「函數合成(compose)」。簡而言之就是,將多箇中間件函數合成一個函數,最後只執行一次。redux

redux 中間件的註冊方式是一次性的,無需屢次調用 .use 或其它用於註冊的函數:數組

applyMiddleware(middleware1, middlwware2, ...);
複製代碼

來看看 applyMiddleware 實現:app

// applyMiddleware.js
// middlewareAPI 是咱們注入中間件的一些東西
const chain = middlewares.map(middleware => middleware(middlewareAPI))

// compose 全部中間件,以後一次性調用 dispatch
dispatch = compose(...chain)(store.dispatch)
複製代碼

compose 在這裏不在贅述。想了解的老鐵們能夠去瞅下 阮一峯的函數式編程入門框架

koa 中間件

將註冊的中間件存到 middleware 數組中,執行時調用 compose 以後的函數。koa

// application.js
class Application {
  use(fn) {
    // 註冊時
    this.middleware.push(fn);
  }
  callback() {
    // compose 成一個 fn 函數調用
    const fn = compose(this.middleware);
  }
}
複製代碼

koa-compose 與函數式編程概念中的 compose 略有不一樣,採用「遞歸 + Promise」的方式,造成了其獨特的「洋蔥模型」。async

// koa-compose/index.js
return dispatch(0);

function dispatch(i) {
  let fn = middleware[i];

  // 終止條件
  if (!fn) return Promise.resolve();

  // 遞歸調用
  // dispatch 就是往中間件注入的 next
  // 從這裏咱們也能夠看到爲何寫中間件時必定要調用 next 方法,
  // 若是不調用的話,遞歸不會繼續,以後的而中間件也不執行,一直 pending
  return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
}
複製代碼

這樣也很難理解爲何 await next() 以後的代碼會反序執行,來看個簡單的例子:函數式編程

async function foofoo() {
  console.log(2);
  await Promise.resolve();
  console.log(2);
}

async function foo() {
  console.log(1);
  await foofoo();
  console.log(1);
}

a(); // 1 2 2 1
複製代碼

仍是隻可意會,不可言傳[奸笑]。關鍵點在於遞歸

express 中間件

註冊成數組(額外的實例化成 layer)形式,調用時從第一個開始執行,執行完畢後調用 next 找到第二個,第二個執行完後 next 第三個,直至最後。

express 中間件的應用實際上是到 router 上面,全部咱們直接跳到路由的設計:

// router/index.js
// 註冊時
proto.use = function use(fn) {
  // 實例化 layer
  var layer = new Layer(options, fn);

  this.stack.push(layer);
};

// 執行時
proto.handle = function handle(req, res, next) {
  // 生成引用。要注意 this 這個坑
  var stack = this.stack;

  next();

  // 這個 next 就是咱們注入了中間件裏
  function next(err) {
    while (match !== true && idx < stack.length) {
      layer = stack[idx ++];
      match = matchLayer(layer, path);

      // 若是你調用 next(1) 的話,這裏會一直不匹配!
      // 最後直接跳事後面的中間件返回 1
      if (layerError) {
        match = false;
        continue;
      }
    }

    // 找到後
    // 其實內部也就是執行咱們的 middleware 函數
    return layer.handle_request(req, res, next);
  }
};

複製代碼

跟 for 循環很像,可是又依賴於開發者手動 next 觸發下一個。

so ?

講了半天中間件,和我要的 compose 有半毛錢關係哦。

  1. redux 用到了精髓
  2. koa 有點皮毛東西
  3. express 八竿子打不着

強行 pick 一波:若是你還在古老地循環調用一系列函數,不妨 compose 試下。

相關文章
相關標籤/搜索