zoukankan      html  css  js  c++  java
  • koa-compose源码阅读与学习

    源码仓库:koa-compose

    前言

    文章开始之前来做一道题目。给一个函数数组,封装一个函数可以依次执行这个函数数组里的函数

    function func1() {
      console.log(1)
    }
    function func2() {
      console.log(2)
    }
    function func3() {
      console.log(3)
    }
    const arr=[func1,func2,func3]
    写一个compose函数,当我们调用compose的时候,依次执行func1、func2、func3,打印出1,2,3,4
    function compose(){
      //your code goes here...
    }
    

    我们很快就能想到使用循环遍历数据,依次执行

    function compose() {
      for (let item of arr) {
        item()
      }
    }
    compose()
    //打印输出:
    //1
    //2
    //3
    

    当然这不是我们想要的答案,我们想要函数这样执行func3(func2(func1()))。可以这样写代码

    //法一:使用reduce,代码简洁
    function compose1() {
      return arr.reduce((prev, curr) => (...args) => curr(prev(...args)))
    }
    
    // 法二:也可以使用循环遍历赋值
    function compose2(){
      let prev
      for(let i=0;i<arr.length;i++){
        prev=arr[i](prev)
      }
      return prev || function(){}
    }
    

    我们变化一下,给函数传入参数,题目变成下面这样

    function func1(next){
      console.log(1)
      next()
      console.log(2)
    }
    function func2(next){
      console.log(3)
      next()
      console.log(4)
    }
    function func3(next){
      console.log(5)
      next()
      console.log(6)
    }
    const arr=[func1,func2,func3]
    写一个compose函数,当我们调用composeSync的时候,打印出1,3,5,6,4,2
    function composeSync(){
      //your code goes here...
    }
    

    我们先来分析一下题目,每个函数都带有next参数,并且next是一个函数,又因为打印输出的顺序可知,next是数组下一个项。也就是说compose函数需要把arr数组里的每一项都串联起来并把后一项当作参数传入当前项执行,所以前半部分会输出1,3,5.又因为都是同步的代码,所以next()都执行完之后才会执行后面的代码所以输出6,4,2。分析完了之后我们可以开始写代码了,最容易让人想到的方式是递归

    //方法一:使用递归
    const composeSync1=function(){
      function dispatch(index){
        if(index===arr.length) return ;
        return arr[index](()=>dispatch(index+1))
      }
      return dispatch(0)
    }
    //方法二:使用循环(一般能用递归的都能使用循环)
    const composeSync2=function(){
      let prev=()=>{ }
      for(let i=arr.length-1;i>=0;i--){
        prev=arr[i].bind(this,prev)
      }
      return prev()
    } 
    composeSync1()
    composeSync2()
    
    //打印输出:
    //1
    //3
    //5
    //6
    //4
    //2
    

    不知不觉我们已经把洋葱模型基本实现了,只要稍加完善(容错处理、异步处理等等)即可使用。下一步我们加上错误处理和异步处理,这个可以参考源码

    //容错:判断一下arr是否是数组,判断arr每一项是否是函数,判断数组长度是否大于0,try...catch()...下衔接项
    //异步:async...await
    

    koa-compose解析

    我们先来字面上理解一下,compose是组合组成的意思,它的作用正是实现洋葱模型,管理所有中间件的。koa-compose是koa的一个中间件,它主要是实现洋葱模型。通过上面的几道题目,可以模糊认为compose就是洋葱模型的雏型,数组里面的每个函数就是一个中间件,洋葱模型的执行机制以及中间件的管理方式。那什么是洋葱模型呢?什么是中间件呢?

    洋葱模型与中间件

    • 洋葱模型:对数据进行串行处理的一种机制,类似于洋葱,被一层层中间件(处理数据的)包裹。
    • 中间件:处理数据的函数、类、方法,分散在模型的各个部位。
      盗图两张:

    compose

    middleware

    源码解析

    '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)
          }
        }
      }
    }
    

    我们先忽略注释,实际代码20行不到,非常精简。首先判断一下传进来的middleware是不是数组并循环一下判断每一项是不是函数。然后在return一个函数传进来参数context(上下文对象)、next(下一步要执行的函数,也就是中间件middleware相比当前项的下一项)。定义dispatch函数用于递归将func1,func2,func3封装成func3(func2(func1))这种结构。首先要判断边界,通过index和middleware长度进行比较,还定义了一个index,用于判断当前的中间件是否已经有过。最后return当前项,把下一项当作参数传给当前项,这样就能保证所有中间件都能嵌套完成。

    手动实现一个洋葱模型

    实现步骤与思路就是我们刚开始做的那几道题目,一步一步做过来即可实现一个简易版本的洋葱模型。最后贴一下代码

    const app = { middlewares: [] };
    app.use = (fn) => {
       app.middlewares.push(fn);
    };
    
    app.compose = function() {
      // Your code goes here
      function dispatch(index){
        if(index===app.middlewares.length) return ;
        const fn=app.middlewares[index];
        return fn(()=>dispatch(index+1))
      }
      dispatch(0);
    }
    app.use(next => {
       console.log(1);
       next();
       console.log(2);
    });
    app.use(next => {
       console.log(3);
       next();
       console.log(4);
    });
    app.use(next => {
       console.log(5);
       next();
       console.log(6);
    });
    app.compose();
    

    参考

  • 相关阅读:
    测试一个纸杯需要考虑什么?
    第三十三天-rsync高级同步工具深度实战
    运维人员必须熟悉的运维工具汇总
    Mysql 高负载排查思路
    查看mysql主从配置的状态及修正 slave不启动问题
    第三十二天-rsync高级同步工具基础
    第三十一天-两个例题
    linux mail 命令参数
    第三十天-ssh key企业批量分发自动化管理案例
    第二十九天-ssh服务重要知识深入浅出讲解
  • 原文地址:https://www.cnblogs.com/xingguozhiming/p/13286859.html
Copyright © 2011-2022 走看看