Koa 洋葱模型

JS世界中无论是Express、Koa、Redux中都会有中间件实现。其实这个中间件主要是为了方便的将业务逻辑拼装成一个逻辑处理链条。其实是对设计模式中责任链模式的一种实现。我们通常也会将他称为洋葱圈模型。
zy-5- koa-compose - 图1

洋葱圈的产生

责任链模式,责任链模式相当于给核心任务加上了一次层层的洋葱圈。
zy-5- koa-compose - 图2
相当于将事前处理、事后处理和业务程序编成了链条
zy-5- koa-compose - 图3
在测试用例上加上 only 修饰,only由jest提供
// 例如
it.only(‘should work’, async () => {})
这样我们就可以只执行当前的测试用例,不关心其他的,不会干扰调试。

compose/test/test.js 测试用例

// compose/test/test.js
‘use strict’

/ eslint-env jest /

const compose = require(‘..’)
const assert = require(‘assert’)

function wait (ms) {
return new Promise((resolve) => setTimeout(resolve, ms || 1))
}
// 分组
describe(‘Koa Compose’, function () {
it.only(‘should work’, async () => {
const arr = []
const stack = []

  1. stack.push(async (context, next) => {<br /> arr.push(1)<br /> await wait(1)<br /> await next()<br /> // 碰上 next 就会去执行 下面一个函数 也就是下面的 “这个函数” 以此类推<br /> await wait(1)<br /> arr.push(6)<br /> })
  2. // 这个函数<br /> stack.push(async (context, next) => {<br /> arr.push(2)<br /> await wait(1)<br /> await next()<br /> await wait(1)<br /> arr.push(5)<br /> })
  3. stack.push(async (context, next) => {<br /> arr.push(3)<br /> await wait(1)<br /> await next()<br /> await wait(1)<br /> arr.push(4)<br /> })
  4. await compose(stack)({})<br /> // 最后输出数组是 [1,2,3,4,5,6]<br /> expect(arr).toEqual(expect.arrayContaining([1, 2, 3, 4, 5, 6]))<br /> })<br />}

koa的文档上有个非常代表性的中间件 gif 图。
zy-5- koa-compose - 图4
而compose函数作用就是把添加进中间件数组的函数按照上面 gif 图的顺序执行。

function compose (middleware) {
// middleware 必须为数组
if (!Array.isArray(middleware)) throw new TypeError(‘Middleware stack must be an array!’)
// fn 必须为函数
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
// 从下标 0 开始
return dispatch(0)

function dispatch (i) {
// 不能连续调用
if (i <= index) return Promise.reject(new Error(‘next() called multiple times’))
index = i
let fn = middleware[i]
// 最后 相等,next 为 undefined 没有下一个函数调用
if (i === middleware.length) fn = next
// 直接返回 Promise.resolve()
if (!fn) return Promise.resolve()
try {
// 也可以 return Promise.resolve(fn(context, () => dispatch( i + 1)))
// 递归调用
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
} catch (err) {
return Promise.reject(err)
}
}
}
}

【注】:值得一提的是:bind函数是返回一个新的函数。第一个参数是函数里的this指向(如果函数不需要使用this,一般会写成null)。 这句fn(context, dispatch.bind(null, i + 1),i + 1 是为了 let fn = middleware[i] 取middleware中的下一个函数。 也就是 next 是下一个中间件里的函数。也就能解释上文中的 gif图函数执行顺序。 测试用例中数组的最终顺序是[1,2,3,4,5,6]。

reduce对compose实现

const compose = (…funcs) => funcs.reduce((a, b) => (…args) => a(b(…args)))

总结
对洋葱模型有了一定的理解

感受
之前学习大多数都是看视频,没咋动手,导致调式还是有点不熟悉