Mercer-Lee的空间

vuePress-theme-reco Mercer-Lee的空间    2018 - 2024
Mercer-Lee的空间 Mercer-Lee的空间

Choose mode

  • dark
  • auto
  • light
TimeLine
分类
  • 数据结构和算法
  • 后端
  • 运维
  • 前端
  • 工具
  • 语言
标签
我的GitHub (opens new window)
author-avatar

Mercer-Lee的空间

27

文章

29

标签

TimeLine
分类
  • 数据结构和算法
  • 后端
  • 运维
  • 前端
  • 工具
  • 语言
标签
我的GitHub (opens new window)
  • Koa的中间件源码解析

    • 中间件
      • 结语

      Koa的中间件源码解析

      vuePress-theme-reco Mercer-Lee的空间    2018 - 2024

      Koa的中间件源码解析


      Mercer-Lee的空间 2020-05-09 JS Koa Node

      # 中间件

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

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

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

      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 (opens new window)也是借鉴了这种设计,react开发者或者感兴趣的朋友可以去看下。

      # 结语

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