Compose應用

諸葛林發表於2019-04-03

在用 PHP 開發自己部落格後臺的時候,碰到一個問題:一般的後臺框架都會提供中介軟體,給使用者自定義額外的功能,so,PHP 應該如何整合一系列的功能中介軟體呢?

你可能想到的實現

註冊中介軟體的時候將其存到陣列中,執行時遍歷陣列。

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 中介軟體的註冊方式是一次性的,無需多次呼叫 .use 或其它用於註冊的函式:

applyMiddleware(middleware1, middlwware2, ...);
複製程式碼

來看看 applyMiddleware 實現:

// applyMiddleware.js
// middlewareAPI 是我們注入中介軟體的一些東西
const chain = middlewares.map(middleware => middleware(middlewareAPI))

// compose 所有中介軟體,之後一次性呼叫 dispatch
dispatch = compose(...chain)(store.dispatch)
複製程式碼

compose 在這裡不在贅述。想了解的老鐵們可以去瞅下 阮一峰的函數語言程式設計入門

koa 中介軟體

將註冊的中介軟體存到 middleware 陣列中,執行時呼叫 compose 之後的函式。

// application.js
class Application {
  use(fn) {
    // 註冊時
    this.middleware.push(fn);
  }
  callback() {
    // compose 成一個 fn 函式呼叫
    const fn = compose(this.middleware);
  }
}
複製程式碼

koa-compose 與函數語言程式設計概念中的 compose 略有不同,採用“遞迴 + Promise”的方式,形成了其獨特的“洋蔥模型”。

// 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 試下。

相關文章