Skip to content

中间件

Node 的 web 框架要讲最有魅力的设计那肯定就是“中间件”了,无论是 express 还是 koa(egg 是在 koa 的基础上开发的框架),他们的中间件都是一大特色,特别是 koa,无数的 koa 使用者就是看中了 koa 的“洋葱模型中间件”才使用 koa 的,接下来我们就用源码来解释洋葱中间件的原理:

javascript
"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, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err);
      }
    }
  };
}

代码很少,甚至于核心代码不过十多行,我在代码上写上注释,基本就能知道怎么实现的了:

javascript
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!")
  }

  return function(context, next) {
    // index初始值为-1
    let index = -1

    // 默认第一个中间件函数执行
    return dispatch(0)

    function dispatch(i) {
      // 如果执行两次next函数的时候报错
      if (i <= index) return Promise.reject(new Error("next() called multiple times"))

      //   设置当前中间件函数
      index = i
      let fn = middleware[i]
      
      //   当i是中间件数组最后一个索引的时候,fn为underfind,直接返回一个空值的Promise对象
      if (i === middleware.length) fn = next
      if (!fn) return Promise.resolve()

      //   接下来就是核心代码,返回的就是一个Promise,当走下去没有next()的时候,执行堆栈将会退出,从最后一个的中间件开始恢复上游的行为,所以形成了洋葱模型,不懂堆栈的可以补下关于栈的知识点。
      try {
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

本身compose返回的就是一个Promise,然后通过中间件的await next()去控制,next()其实就是下一个中间件函数,所以等待下一个中间件执行完成之后才会执行后面的内容。其实redux-thunk也是借鉴了这种设计,react开发者或者感兴趣的朋友可以去看下。

结语

框架或者库重要的不是多么花里胡哨的代码,而是设计思想,只要了解了设计思想,你也能写出那些看起来高大上的技术。