Skip to content

koa-compose 分析 #32

@func-star

Description

@func-star

原文链接

介绍

在处理一些复杂逻辑的场景下,特别是一些长流程的任务,我们通常喜欢把流程拆分成单步小任务来执行,然后保证这些小任务按照既定的逻辑顺序执行。

或者我们希望对一个实体赋能,让它拥有错误检测、数据过滤、日志打印等功能。把每一项功能都抽成独立的中间件,分别负责独立的任务,就是一个比较好的选择。

koa-compose 模块可以将多个中间件函数合并成一个组合中间件函数,然后通过调用这个中间件函数就可以依次来执行这一系列中间件。

这个模块的源码比较简单,我们直接通过源码来分析一下它的处理过程:

'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) {
    // 只接收 function
    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]
      // 当遍历完所有的中间件,执行 next 回调函数
      if (i === middleware.length) fn = next
      // 如果没有定义 next,直接 返回 undefined
      if (!fn) return Promise.resolve()
      try {
        // 每一个中间件都会有两个形参
        // 1.外部透传进来的 context 对象,2.next 回调方法
        // 下一个中间件的执行体,会作为上一个中间的 next 参数传递进去
        // 通过 next() 方法的调用,实现递归所有中间件
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

通过代码我们可以看出 compose 内部的中间件依次执行是通过 next() 来实现的。可以理解成接着往下执行,也可以表示执行下一个中间件。

next

const compose = require('koa-compose')

var compMiddleware = compose([
	middleware1,
	middleware2
])

var compMiddlewareNext = function () {
	console.log('中间件全部执行完毕')
}

compMiddleware('我是 context', compMiddlewareNext)

function middleware1(ctx, next) {
	console.log(ctx)
	console.log('第一个中间件')
	next()
	console.log('第一个中间件的next执行后')
}

function middleware2(ctx, next) {
	console.log(ctx)
	console.log('第二个中间件')
	next()
	console.log('第二个中间件的next执行后')
}
  • 输出
我是 context
第一个中间件
我是 context
第二个中间件
中间件全部执行完毕
第二个中间件的next执行后
第一个中间件的next执行后

上述代码的执行过程如下:

  1. 执行 middleware1,输出“第一个中间件”,调用 next()
  2. 执行 middleware2,输出“第二个中间件”,调用 next()
  3. 执行 compMiddlewareNext,输出“中间件全部执行完毕”
  4. 全部中间件执行完毕,执行步骤回到 middleware2 的执行体,输出“第二个中间件的next执行后”
  5. middleware2 执行完毕后,middleware1 继续执行,输出“第一个中间件的next执行后”

通过下面一段伪代码可能会让执行过程更加清晰一些:

compMiddleware {
    middleware1 {
        console.log('第一个中间件')
        // next() 调用的时候执行 middleware2
        middleware2 {
            console.log('第二个中间件')
            // next()调用的时候执行 compMiddlewareNext()
            console.log('中间件全部执行完毕')
            console.log('第二个中间件的next执行后')
        }
        console.log('第一个中间件的next执行后')
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions