JavaScript异步编程:异步数据收集的具体方法


Posted in Javascript onAugust 19, 2013

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);
});

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

 

何时抛出亦无妨?

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

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

Async.js的函数式写法
我们想把同步迭代器所使用的filter和forEach方法替换成相应的异步方法。Async.js给了我们两个选择。

async.filter和async.forEach,它们会并行处理给定的数组。
async.filterSeries和async.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)。

filter和forEach并不是仅有的与标准函数式迭代方法相对应的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/filterSeries、reject/rejectSeries、detect/detectSeries、some、every等)的迭代器均无法报告错误。

对于非布尔型的所有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 相关文章推荐
js获取GridView中行数据的两种方法 分享
Jul 13 Javascript
点击页面其它地方隐藏该div的两种思路
Nov 18 Javascript
优化javascript的执行效率一些方法总结
Dec 25 Javascript
angularjs表格ng-table使用备忘录
Mar 09 Javascript
JS 实现倒计时数字时钟效果【附实例代码】
Mar 30 Javascript
使用jQuery制作基础的Web图片轮播效果
Apr 22 Javascript
webpack配置的最佳实践分享
Apr 21 Javascript
纯js实现图片匀速淡入淡出效果
Aug 22 Javascript
js使用原型对象(prototype)需要注意的地方
Aug 28 Javascript
基于angular-utils-ui-breadcrumbs使用心得(分享)
Nov 03 Javascript
Three.js 再探 - 写一个微信跳一跳极简版游戏
Jan 04 Javascript
Angular性能优化之第三方组件和懒加载技术
May 10 Javascript
文本框中禁止非数字字符输入比如手机号码、邮编
Aug 19 #Javascript
JQUERY 获取IFrame中对象及获取其父窗口中对象示例
Aug 19 #Javascript
时间戳转换为时间 年月日时间的JS函数
Aug 19 #Javascript
详解JavaScript函数绑定
Aug 18 #Javascript
jQuery 绑定事件到动态创建的元素上的方法实例
Aug 18 #Javascript
jQuery焦点图切换特效插件封装实例
Aug 18 #Javascript
JavaScript生成GUID的多种算法小结
Aug 18 #Javascript
You might like
php中filter_input函数用法分析
2014/11/15 PHP
php 反斜杠处理函数addslashes()和stripslashes()实例详解
2016/12/25 PHP
php求数组全排列,元素所有组合的方法总结
2017/03/14 PHP
PHP利用Socket获取网站的SSL证书与公钥
2017/06/18 PHP
初学Javascript的一些总结
2008/11/03 Javascript
关于js new Date() 出现NaN 的分析
2012/10/23 Javascript
jQuery控制元素显示、隐藏、切换、滑动的方法总结
2015/04/16 Javascript
AngularJS使用ngMessages进行表单验证
2015/12/27 Javascript
基于Bootstrap实现图片轮播效果
2016/05/22 Javascript
关于jquery中attr()和prop()方法的区别
2018/05/28 jQuery
vue中v-model的应用及使用详解
2018/06/27 Javascript
RxJS的入门指引和初步应用
2019/06/15 Javascript
bootstrap 日期控件 datepicker被弹出框dialog覆盖的解决办法
2019/07/09 Javascript
vue quill editor 使用富文本添加上传音频功能
2020/01/14 Javascript
JS数组的高级使用方法示例小结
2020/03/14 Javascript
[02:22]2018DOTA2亚洲邀请赛VG赛前采访
2018/04/03 DOTA
[11:33]DAC2018 4.5SOLO赛决赛 MidOne vs Paparazi第二场
2018/04/06 DOTA
[03:30]完美盛典趣味短片 CSGO2019年度名场面
2019/12/07 DOTA
python实现bitmap数据结构详解
2014/02/17 Python
python中__call__方法示例分析
2014/10/11 Python
Python深度优先算法生成迷宫
2018/01/22 Python
Python2与Python3的区别点整理
2019/12/12 Python
Python greenlet和gevent使用代码示例解析
2020/04/01 Python
Python 基于jwt实现认证机制流程解析
2020/06/22 Python
世界领先的豪华床上用品供应商之一:Bedeck Home
2019/03/18 全球购物
中国专业的音频分享平台:喜马拉雅
2019/05/24 全球购物
初始化了一个没有run()方法的线程类,是否会出错?
2014/03/27 面试题
土木工程专业个人求职信
2013/12/05 职场文书
幼儿园的门卫岗位职责
2014/04/10 职场文书
大学生村官演讲稿
2014/04/25 职场文书
2014年绿化工作总结
2014/12/09 职场文书
行政诉讼答辩状
2015/05/21 职场文书
信用卡催款律师函
2015/05/27 职场文书
工作感想范文
2015/08/07 职场文书
纪念建国70周年演讲稿
2019/07/19 职场文书
3050和2060哪个好 性能差多少 差距有多大 谁更有性价比
2022/06/17 数码科技