用aa自己实现一个waterfall函数

192 阅读2分钟

async.js 关于 waterfall 的文档: caolan.github.io/async/v3/do…

什么是 waterfall

依次运行数组中的函数,然后传递该函数的结果到下一个函数。
如果任何一个函数执行失败并把失败传入回调函数,后边的函数一律不执行,并且立即调用第二个参数的函数。

# 官方示例
# 第一个参数是函数的数组,第二个参数是最终的回调函数【可不传】
# 之所以使用 callback 函数作为数组中的函数的参数,是因为在这些函数中可能要执行一些异步的任务,
# 如果直接用 return 返回值的方式,就不必用 waterfall 了,一个个顺序执行这些函数即可。
# 而且 waterfall 这种方式也兼容同步的任务,把返回值传入 callback 函数即可。
async.waterfall([
    function(callback) {
        callback(null, 'one', 'two');
    },
    function(arg1, arg2, callback) {
        // arg1 now equals 'one' and arg2 now equals 'two'
        callback(null, 'three');
    },
    function(arg1, callback) {
        // arg1 now equals 'three'
        callback(null, 'done');
    }
], function (err, result) {
    // result now equals 'done'
});

编码实现【用aa实现】

/**
 * 定义 waterfall 序列执行任务包装函数
 *
 * @param tasks
 * @param cb
 * @returns {Promise<*>}
 */
async function waterfall(tasks, cb) {
  // 如果用户不传入第二个参数,这里补一个默认的
  cb = cb || function (err){
    if (err) {
      console.error(err)
    } else {
      console.log('done')
    }
  }

  // 如果没有任务,直接结束
  if (!Array.isArray(tasks) || !tasks.length) {
    return cb()
  }

  // 任何一个任务失败直接进入 catch, 后续函数也就不会执行了
  try {
    // 初始化传入每一个函数的数据,第一个函数没有人传给他数据,所以是空的
    let args = []
    
    // 使用for循环以及 await 去逐个执行任务,并得到其调用 callback 时需要传入的返回值
    for (let i = 0; i < tasks.length; i++) {
      let fn = tasks[i]
      
      // 判断不是函数时直接失败
      if ((typeof fn) !== 'function') {
        throw 'tasks must be functions'
      }
      
      args = await new Promise((resolve, reject) => {
        
        // 用户数组中函数的 callback 其实是我们这个 waterfall 函数给定义的
        // 对用户来说 callback 是个黑盒子,他只需要知道调用它,就可以中断流程或把想要的值传入下一个函数
        // callback 函数用 ...data 的方式收集用户调用函数是传入的参数【因为参数数目不确定】。
        // callback 函数的作用就是把上一步的失败 reject 掉或 data 给resolve 出来给 args。
        fn.call(this, ...args, function (err, ...data) {
          console.log(`err: ${err} data: ${data}`)
          if (err) {
            reject(err)
          } else {
            resolve(data)
          }
        })}
      )
    }

    // 执行完成后,执行最终的 cb
    cb(null)
  } catch(err){
    cb(err)
  }
}

测试

/**
 * 调用测试
 *
 */
waterfall([
  function(callback) {
    console.log('函数1')
    callback(null, 'one', 'two');
  },
  function(arg1, arg2, callback) {
    console.log('函数2')
    // arg1 now equals 'one' and arg2 now equals 'two'
    callback(null, 'three');
  },
  function(arg1, callback) {
    console.log('函数3')
    // arg1 now equals 'three'
    callback(null, 'done');
  }
], function (err, result) {
  // result now equals 'done'
  if (err) {
    console.error(err)
  } else {
    console.log('用户传入cb done')
  }
})