compose函数的作用就是组合函数,依次组合传入的函数,控制函数的执行顺序

  1. function compose (middleware) {
  2. //判断middleware是否为函数数组
  3. if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
  4. for (const fn of middleware) {
  5. if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
  6. }
  7. //返回一个匿名函数
  8. return function (next) {
  9. let index = -1
  10. return dispatch(0)
  11. function dispatch (i) {
  12. if (i <= index) return Promise.reject(new Error('next() called multiple times'))
  13. //根据下文可知index是从-1递增
  14. index = i
  15. let fn = middleware[i]
  16. //如果函数数组被遍历完,并且i===middleware.length,此时fn 应该为undefined;之后把next函数赋值给fn
  17. if (i === middleware.length) fn = next
  18. //结束递归条件,返回promise
  19. if (!fn) return Promise.resolve()
  20. try {
  21. /*
  22. 实现递归,执行fn,并返回promise,这里需要注意与常用的递归实现方法不同,
  23. dispatch函数对自身的调用是通过对fn的传参,这样通过递归实现了对middleware
  24. 的遍历,fn的执行顺序就如下图的洋葱模型一样,这也是koa的经典模型
  25. */
  26. return Promise.resolve(fn(dispatch.bind(null, i + 1)));
  27. } catch (err) {
  28. return Promise.reject(err)
  29. }
  30. }
  31. }
  32. }

image.png
koa洋葱模型

测试:

  1. let fun1 = function (next) {
  2. console.log(1)
  3. /*
  4. 执行传入的next函数,next在这里相当于compose里的dispatch函数,执行
  5. next就相当于dispatch对自身的调用,从而可以继续执行middleware当中剩
  6. 下的fn函数。由于返回的是promise,所以可以异步等待next的执行,
  7. */
  8. next()
  9. /*
  10. next执行完毕才会打印后面的内容,这里并不会直接打印'next1',而是等待后
  11. 边的函数先执行
  12. */
  13. console.log('next1')
  14. }
  15. let fun2 = function (next) {
  16. console.log(2)
  17. next()
  18. console.log('next2')
  19. }
  20. let fun3 = function (next) {
  21. console.log(3)
  22. /*
  23. 由于fun3是middleware数组当中的最后一个函数,所以传入的next函数为
  24. compose返回的匿名函数的参数,在这里为say函数,所以会在3和next3当
  25. 中打印1000
  26. */
  27. next()
  28. console.log('next3')
  29. }
  30. let say = function () {
  31. console.log(1000)
  32. }
  33. compose([fun1, fun2, fun3])(say)

打印结果 1,2,3,1000,next3,next2,next1

参考源码地址:https://github.com/koajs/compose/blob/master/index.js