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();

参考

posted @ 2020-07-12 02:01  mingL  阅读(178)  评论(0编辑  收藏  举报