我们先尝试在不借助任何工具函数的情况下来解决这个问题。笔者能想到的最简单的方法是:因前一个readFile的回调运行下一个readFile,同时跟踪记录迄今已触发的回调次数,并最终显示输出。下面是笔者的实现结果。

Asyncjs/seriesByHand.js

var fs = require('fs');
process.chdir('recipes'); // 改变工作目录
var concatenation = '';
fs.readdir('.', function(err, filenames) {if (err) throw err;
function readFileAt(i) {var filename = filenames[i];
fs.stat(filename, function(err, stats) {if (err) throw err;
if (! stats.isFile()) return readFileAt(i + 1);
fs.readFile(filename, 'utf8', function(err, text) {if (err) throw err;
concatenation += text;
if (i + 1 === filenames.length) {// 所有文件均已读取,可显示输出
return console.log(concatenation);
}
readFileAt(i + 1);
});
});
}
readFileAt(0);
});

如你所见,异步版本的代码要比同步版本多很多。如果使用filterforEach这些同步方法,代码的行数大约只有一半,而且读起来也要容易得多。如果这些漂亮的迭代器存在异步版本该多好啊!使用Async.js就能做到这一点!

何时抛出亦无妨?

大家可能注意到了,在上面那个代码示例中笔者无视了自己在第1.4节中提出的建议:从回调里抛出异常是一种糟糕的设计,尤其在成品环境中。不过,一个简单如斯的示例直接抛出异常则完全没有问题。如果真的遇到代码出错的意外情形,throw会关停代码并提供一个漂亮的堆栈轨迹来解释出错原因。

这里真正的不妥之处在于,同样的错误处理逻辑(即if(err) throw err)重复了多达3次!在4.2.2节,我们会看到Async.js如何帮助减少这种重复。

Async.js的函数式写法

我们想把同步迭代器所使用的filterforEach方法替换成相应的异步方法。Async.js给了我们两个选择。

  • async.filterasync.forEach,它们会并行处理给定的数组。
  • async.filterSeriesasync.forEachSeries,它们会顺序处理给定的数组。

并行运行这些异步操作应该会更快,那为什么还要使用序列式方法呢?原因有两个。

  • 前面提到的工作流次序不可预知的问题。我们确实可以先把结果存储成数组,然后再joining(联接)数组来解决这个问题,但这毕竟多了一个步骤。
  • Node及其他任何应用进程能够同时读取的文件数量有一个上限。如果超过这个上限,操作系统就会报错。如果能顺序读取文件,则无需担心这一限制。

所以现在先搞明白async.forEachSeries再说。下面使用了Async.js的数据收集方法,直接改写了同步版本的代码实现。

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) {async.forEachSeries(filenames, readAndConcat, onComplete);
});
function isFilename(filename, callback) {fs.stat(filename, function(err, stats) {if (err) throw err;
callback(stats.isFile());
});
}
function readAndConcat(filename, callback) {fs.readFile(filename, 'utf8', function(err, fileContents) {if (err) return callback(err);
concatenation += fileContents;
callback();
});
}
function onComplete(err) {if (err) throw err;
console.log(concatenation);
}

现在我们的代码漂亮地分成了两个部分:任务概貌(表现形式为async.filter调用和async.forEachSeries调用)和实现细节(表现形式为两个迭代器函数和一个完工回调onComplete)。

filterforEach并不是仅有的与标准函数式迭代方法相对应的Async.js工具函数。Async.js还提供了以下方法:

  • reject/rejectSeries,与filter刚好相反;
  • map/mapSeries,1:1变换;
  • reduce/reduceRight,值的逐步变换;
  • detect/detectSeries,找到筛选器匹配的值;
  • sortBy,产生一个有序副本;
  • some,测试是否至少有一个值符合给定标准;
  • every,测试是否所有值均符合给定标准。

这些方法是Async.js的精髓,令你能够以最低的代码重复度来执行常见的迭代工作。在继续探索更高级的方法之前,我们先来看看这些方法的错误处理技术。

Async.js的错误处理技术

要怪就怪Node的fs.exists首开这一先河吧!而这也意味着使用了Async.js数据收集方法(filter/filterSeriesreject/rejectSeriesdetect/detectSeriessomeevery等)的迭代器均无法报告错误。

对于非布尔型的所有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的所有其他方法而言,遵守这些约定可以让错误干净利落地从各个任务流向完工回调。下一节会看到更多这样的例子。

JavaScript异步编程:异步的数据收集方法相关推荐

  1. Javascript异步编程的4种方法

    你可能知道,Javascript语言的执行环境是"单线程"(single thread). 所谓"单线程",就是指一次只能完成一件任务.如果有多个任务,就必须排 ...

  2. [转载]Javascript异步编程的4种方法

    NodeJs的最大特性就是"异步" 目前在NodeJs里实现异步的方法中,使用"回调"是最常见的. 其实还有其他4种实现异步的方法: 在此以做记录 --- ht ...

  3. (转)javascript异步编程的四种方法

    本文转自:http://www.ruanyifeng.com/blog/2012/12/asynchronous%EF%BC%BFjavascript.html 作者:阮一峰 本文仅仅作为个人mark ...

  4. JavaScript异步编程的四种方法(转)

    作者: 阮一峰 日期: 2012年12月21日 你可能知道,Javascript语言的执行环境是"单线程"(single thread). 所谓"单线程",就是 ...

  5. JavaScript异步编程【上】 -- 同步和异步、事件循环(EventLoop)、微任务和宏任务、回调函数

    文章内容输出来源:拉勾教育 大前端高薪训练营 前言 在我们学习JavaScript中,我们知道,JavaScript的执行环境是单线程的.所谓单线程是指一次只能完成一个任务,如果有多个任务,就必须排队 ...

  6. JavaScript异步编程原理

    众所周知,JavaScript 的执行环境是单线程的,所谓的单线程就是一次只能完成一个任务,其任务的调度方式就是排队,这就和火车站洗手间门口的等待一样,前面的那个人没有搞定,你就只能站在后面排队等着. ...

  7. 前端JavaScript 异步编程详解

    目录 菜鸟教程官网 JavaScript 异步编程 异步的概念 详图 什么时候用异步编程 回调函数 概念 例如: 最后 菜鸟教程官网 地址 JavaScript 异步编程 异步的概念 异步(Async ...

  8. Javascript异步编程方法有哪些

    Javascript 语言的执行环境是"单线程"(single thread).所谓"单线程",就是指一次只能完成一件任务.如果有多个任务,就必须排队,前面一个 ...

  9. [书籍精读]《JavaScript异步编程》精读笔记分享

    写在前面 书籍介绍:本书讲述基本的异步处理技巧,包括PubSub.事件模式.Promises等,通过这些技巧,可以更好的应对大型Web应用程序的复杂性,交互快速响应的代码.理解了JavaScript的 ...

最新文章

  1. linux下获取系统时间 和 时间偏移
  2. DirectX下 Viewing Frustum 的详细实现
  3. ORACLE DBA的职责
  4. 我为什么选择在北上广深打拼?
  5. 由点及面,专有云ABC Stack如何护航云平台安全?
  6. C#检查json格式是否合法
  7. 二分查找离左边元素最近的(可以等于)
  8. 实例60:python
  9. 用积分来衡量博客的成绩
  10. OpenCV--矩阵操作总结
  11. 【Elasticsearch】elasticsearch allocation 分析
  12. Sublime Text 2中的正则表达式搜索替换
  13. Android 应用程序之间数据共享—ContentProvider
  14. (转)策略回测的框架、实现、测试
  15. 小说阅读器java源代码_Android项目源码任阅小说阅读器高仿追书神器
  16. 如何与朋友同步观看YouTube视频
  17. Python_基础笔记
  18. 使用周期一致的对抗网络进行不成对的图像到图像转换
  19. 又是暴力裁员?腾讯 7 年老员工一朝被裁,官方回应了...
  20. 王者荣耀——bat批处理文件,自动刷金币版(脱胎于30行Python代码刷金币版),Windows双击即可运行!

热门文章

  1. spark2读取oracle工具类,spark读写Oracle、hive的艰辛之路(一)
  2. php如何判断提交内容为空,php不允许用户提交空表单(php空值判断)
  3. C++为什么空格无法输出_算法竞赛C++常用技巧——输入输出优化(防止TLE)
  4. java 去掉 时期中的图片,去除图片浅色背景(Java 实现)
  5. sip 时序图_时序图怎么看_教你如何看懂时序图 - 什么是时序图_时序图怎么看_教你如何看懂时序图...
  6. 将字符数组中的字符按从小到大的顺序排序
  7. 【c语言】蓝桥杯算法训练 大等于n的最小完全平方数
  8. [转帖]在SQL SERVER中实现RSA加密算法
  9. 解决 /var/run/nginx.pid failed
  10. PHP数组式访问接口ArrayAccess