JavaScript异步编程:异步数据收集的具体方法 |
本文标签:异步,数据,收集 Asyncjs/seriesByHand.js 复制代码 代码如下: var fs = require(fs); process.chdir(recipes); // 改变工作目录 var concatenation = ; fs.readdir(., function(err, filenames) { function readFileAt(i) { fs.readFile(filename, utf8, function(err, text) { 如你所见,异步版本的代码要比同步版本多很多 。如果使用filter、forEach这些同步方法,代码的行数大约只有一半,而且读起来也要容易得多 。如果这些漂亮的迭代器存在异步版本该多好啊!使用Async.js就能做到这一点!
何时抛出亦无妨? 大家可能注意到了,在上面那个代码示例中笔者无视了自己在第1.4节中提出的建议:从回调里抛出异常是一种糟糕的设计,尤其在成品环境中 。不过,一个简单如斯的示例直接抛出异常则完全没有问题 。如果真的遇到代码出错的意外情形,throw会关停代码并提供一个漂亮的堆栈轨迹来解释出错原因 。 这里真正的不妥之处在于,同样的错误处理逻辑(即if(err) throw err)重复了多达3次!在4.2.2节,我们会看到Async.js如何帮助减少这种重复 。 Async.js的函数式写法 async.filter和async.forEach,它们会并行处理给定的数组 。 前面提到的工作流次序不可预知的问题 。我们确实可以先把结果存储成数组,然后再joining(联接)数组来解决这个问题,但这毕竟多了一个步骤 。 Asyncjs/forEachSeries.js 复制代码 代码如下: var async = require(async); var fs = require(fs); process.chdir(recipes); // 改变工作目录 var concatenation = ; var dirContents = fs.readdirSync(.); async.filter(dirContents, isFilename, function(filenames) { function isFilename(filename, callback) { function readAndConcat(filename, callback) { function onComplete(err) { 现在我们的代码漂亮地分成了两个部分:任务概貌(表现形式为async.filter调用和async.forEachSeries调用)和实现细节(表现形式为两个迭代器函数和一个完工回调onComplete) 。 filter和forEach并不是仅有的与标准函数式迭代方法相对应的Async.js工具函数 。Async.js还提供了以下方法: reject/rejectSeries,与filter刚好相反; Async.js的错误处理技术 对于非布尔型的所有Async.js迭代器,传递非null/undefined的值作为迭代器回调的首参数将会立即因该错误值而调用完工回调 。这正是readAndConcat不用throw也能工作的原因 。 Asyncjs/forEachSeries.js 复制代码 代码如下: function readAndConcat(filename, callback) { fs.readFile(filename, utf8, function(err, fileContents) { if (err) return callback(err); concatenation += fileContents; callback(); }); } 所以,如果callback(err)确实是在readAndConcat中被调用的,则这个err会传递给完工回调(即onComplete) 。Async.js只负责保证onComplete只被调用一次,而不管是因首次出错而调用,还是因成功完成所有操作而调用 。 Asyncjs/forEachSeries.js 复制代码 代码如下: function onComplete(err) { if (err) throw err; console.log(concatenation); } Node的错误处理约定对Async.js数据收集方法而言也许并不理想,但对于Async.js的所有其他方法而言,遵守这些约定可以让错误干净利落地从各个任务流向完工回调 。下一节会看到更多这样的例子 。 |