4.4 流程控制库

28 阅读2分钟

这一小节是2013年那段时间的“历史经典”,朴灵作者详细介绍了当时几个主流的异步流程控制库:asyncStepwind,以及一个重要的底层机制——尾触发与nextTick。这些库的共同目标都是进一步消灭回调地狱,让串行、并行、限流等复杂流程更易表达。

现在(2025年)来看,这些库基本被 async/await 取代了,但了解它们有两大价值:

  1. 很多老项目、npm包还在用(尤其是async库)。
  2. 能帮助你理解现代async/await的设计灵感来源。

4.4.1 尾触发与nextTick

作者先解释一个关键概念:尾触发(tail trigger)。

  • 在同步函数里,最后一步往往是“触发下一个动作”。
  • 异步环境下,直接调用下一个会造成深层嵌套。
  • 解决思路:把“下一个动作”推到事件队列尾部(用process.nextTick或setImmediate),避免阻塞当前执行流。

这其实就是微任务的雏形,为后面的库铺路。

4.4.2 async库(最经典的一个,至今仍在广泛使用)

async是当时最流行的流程控制库(作者强烈推荐)。

核心API:

  • series:串行执行任务,前一个完成才执行下一个
  • parallel:并行执行所有任务,全部完成调用最终回调
  • waterfall:串行,且前一个任务的结果传给下一个
  • auto:复杂依赖图(支持依赖注入)
  • whilst/until:循环控制

示例:串行读取多个文件(waterfall)

const async = require('async');
const fs = require('fs');

async.waterfall([
  (callback) => fs.readFile('a.txt', 'utf8', callback),
  (dataA, callback) => {
    console.log('a:', dataA);
    fs.readFile('b.txt', 'utf8', callback);
  },
  (dataB, callback) => {
    console.log('b:', dataB);
    callback(null, '最终结果');
  }
], (err, result) => {
  if (err) return console.error(err);
  console.log('完成:', result);
});

并行示例:

async.parallel([
  (cb) => fs.readFile('a.txt', 'utf8', cb),
  (cb) => fs.readFile('b.txt', 'utf8', cb)
], (err, results) => {
  console.log(results[0], results[1]);
});

优点:API丰富、控制粒度细、社区活跃。

4.4.3 Step库

Step库的设计更“线性”,代码看起来像同步:

var Step = require('step');

Step(
  function readA() {
    fs.readFile('a.txt', 'utf8', this);
  },
  function process(err, dataA) {
    if (err) throw err;
    console.log(dataA);
    return '处理结果';
  },
  function readB(err, result) {
    if (err) throw err;
    fs.readFile('b.txt', 'utf8', this);
  }
);

内部用this()作为下一个步骤的回调,自动捕获异常。

4.4.4 wind库(国内特色)

Wind.js是国内开发者尤雨溪(Vue作者)早期作品,尝试用“编译器”实现类似generator的效果(接近现在的async/await):

var readFile = eval(Wind.compile("async", function () {
  var a = $await(fs.readFileAsync('a.txt', 'utf8'));
  var b = $await(fs.readFileAsync('b.txt', 'utf8'));
  console.log(a, b);
}));

readFile().start();

当时很惊艳,但需要预编译,普及度不高。

现代视角(2025年)

这些库的核心思想都被ES2017的async/await完美吸收:

async function main() {
  try {
    const a = await fs.promises.readFile('a.txt', 'utf8');
    const b = await fs.promises.readFile('b.txt', 'utf8');
    console.log(a, b);
  } catch (err) {
    console.error(err);
  }
}
main();

async/await = waterfall + try/catch + Promise 的终极融合。