NodeJS异步调用的顺序化处理

NodeJS本身一个重要的特性就是异步处理,但异步处理多了,会把一个完整的“业务”逻辑拆分的支离破碎,让人看到头大,或许这就是所谓的异步陷阱?!最近看NodeJS项目代码时,发现了一个Step的模块,就是专门将一系列执行有先后关系的异步调用做序列化调用处理的,使之在代码层面上尽量看起来更清晰。Step详情可参见 https://github.com/creationix/step

1. 简单的将异步处理顺序化

step本身定义了一个 Step 的方法, Step可以接收任意多的方法,并按顺序执行这些方法,其中this代表下一个方法的回调。

var fs = require('fs');
var Step = require('step'); 
Step( function readSelf() {
  fs.readFile(__filename, 'utf-8', this); }, 
  function capitalize(err, text) { 
    if (err) throw err; 
    return text.toUpperCase(); 
  }, 
  function showIt(err, newText) { 
    if (err) throw err; 
    console.log(newText); 
  }
);

执行以上代码后,会看到一段大写的代码,从代码逻辑上不难理解:

  1. readSelf(), 使用了异步函数读取方法readFile(), 并通过this来指定下一个方法(capitalize)作为callback函数。
  2. capitalize(), 没有使用异步方法,所以直接通过return调用下一个方法(showIt)
  3. showIt(), 将上一个方法return的值(newText)打印出来

第一个参数固定为一个异常接收的参数,只要你不嵌套回调,这将有助于你捕获所有的异常,保证你的服务足够健壮。

2. 通过this.parallel()实现并行调用

如果在一个逻辑处理,我们需要触发多个异步调用,并写保证这个逻辑的顺序执行,我们可以使用this.parallel() 来代替 this,通过代码我们将很容易明白:

var fs = require('fs');
var Step = require('step');
Step( // Loads two files in parallel
  function loadStuff() { 
    fs.readFile(__filename, 'utf-8', this.parallel());
    fs.readFile("/etc/passwd", 'utf-8', this.parallel()); 
  },
  // Show the result when done function 
  showStuff(err, code, users) { 
    if (err) throw err; 
    console.log(code); 
    console.log(users); 
  } 
)

3. group() 方法

如果在一个方法内,我们需要批量调用同一个回调方法,并对该方法产生的数据有统一的处理模式,那么我们需要使用group()。例如我们需要读取一个目录下所有的js文件,然后再将这些文件的内容打印出来,我们就可以使用group这个方法。看代码会比较好明白:

var fs = require('fs');
var Step = require('step');
Step(
  function readDir() { fs.readdir(__dirname, this); },
  function readFiles(err, results) { 
    if (err) throw err; // Create a new group 
    var group = this.group(); 
    results.forEach(function (filename) { 
      if (/\.js$/.test(filename)) { 
        fs.readFile(__dirname + "/" + filename, 'utf8', group()); } }); }, 
        function showAll(err , files) { 
          if (err) throw err; 
          console.dir(files); 
        } 
);

其中特别需要说明的是,这里的group()一定要“new”出来一个,不可直接使用this.group(),因为此处的group()需要统一管理所有回调的状态,也就是说在 readFiles().results.forEach循环回调读取当前目录下所有文件完成后,再统一调用下一步定义的文件内容处理方法showAll()。