如何将异步函数依次执行?-compose

866 阅读2分钟

「我正在参与掘金会员专属活动-源码共读第一期,点击参与

本篇文章介绍的是compose函数。

可能听起来有点懵圈?下面会一一道来:

在我们一般处理某个业务逻辑时,比如新增、删除、修改记录,在逻辑处理之前首先要校验一下用户的身份,处理完逻辑可能还得记录一下日志或者完成逻辑的耗时等等。这好办,我们直接将这些校验和日志添加到业务逻辑里就好了,这样肯定是不行了,代码冗余。有没有更好的办法呢?

这样就要提到中间件middleware了。中间件的作用就是在请求前和请求后插入逻辑,代码得到复用。

那如何将中间件和请求组合起来执行呢?compose这时候就登场了。这里以koa-compose为例。

koa-compose

安装

$ npm install koa-compose

使用

import compose from "koa-compose"
fn = compose([a, b, c, ...])

传入异步函数数组。

源码

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)
      }
    }
  }
  1. 首先是参数校验,传入的必须是数组,其次每个数组的元素类型必须是函数
  2. 返回值是一个函数, 传入了两个参数,分别是contextnext下一个执行的函数
  3. 返回的函数里声明了一个dispatch函数,首先校验了next多次调用
  4. 然后就是根据索引取到对应的中间件,通过Promise.resolve()执行该中间件,next传入下一个dispatch。并返回resolve,如果报错则返回Promise.reject(err),如果中间件执最后一个执行完毕,则返回Promise.resolve()一个空的Promise对象。

关联的设计模式(责任链模式)

为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到没有对象处理它为止。

结束语

它的核心逻辑在dispatch函数里。每次等待上一个中间件执行完毕之后,就执行一个,链式调用,保证了异步函数按顺序执行。