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 相关文章推荐
Javascript操纵Cookie实现购物车程序
Feb 15 Javascript
对象特征检测法判断浏览器对javascript对象的支持
Jul 25 Javascript
Js日期选择自动填充到输入框(界面漂亮兼容火狐)
Aug 02 Javascript
使用ImageMagick进行图片缩放、合成与裁剪(js+python)
Sep 16 Javascript
jquery 获取dom固定元素 添加样式的简单实例
Feb 04 Javascript
jQuery移除元素自动解绑事件实现思路及代码
May 31 Javascript
yepnope.js使用详解及示例分享
Jun 23 Javascript
jquery通过load获取文件的内容并跳到锚点的方法
Jan 29 Javascript
JavaScript中使用Object.prototype.toString判断是否为数组
Apr 01 Javascript
Bootstrap Paginator分页插件与ajax相结合实现动态无刷新分页效果
May 27 Javascript
JavaScript 判断对象中是否有某属性的常用方法
Jun 14 Javascript
Vue插槽原理与用法详解
Mar 05 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
2006/11/25 PHP
php中选择什么接口(mysql、mysqli)访问mysql
2013/02/06 PHP
深入理解:单一入口、MVC、ORM、CURD、ActiveRecord概念
2013/06/06 PHP
php编译安装php-amq扩展简明教程
2016/06/25 PHP
PHP编写daemon process详解及实例代码
2016/09/30 PHP
Mozilla中显示textarea中选择的文字
2006/09/07 Javascript
javascript dom代码应用 简单的相册[firefox only]
2010/06/12 Javascript
JS判定是否原生方法
2013/07/22 Javascript
jQuery lazyLoad图片延迟加载插件的优化改造方法分享
2013/08/13 Javascript
jQuery中选择器小问题(新人难免遇到)
2014/03/31 Javascript
jquery简单实现带渐显效果的选项卡菜单代码
2015/09/01 Javascript
javascript滚轮控制模拟滚动条
2016/10/19 Javascript
js 数据存储和DOM编程
2017/02/09 Javascript
js实现方块上下左右移动效果
2017/08/17 Javascript
async/await优雅的错误处理方法总结
2019/01/30 Javascript
vue实现动态显示与隐藏底部导航的方法分析
2019/02/11 Javascript
点击按钮弹出模态框的一系列操作代码实例
2019/03/29 Javascript
微信端调取相册和摄像头功能,实现图片上传,并上传到服务器
2019/05/16 Javascript
小程序实现按下录音松开识别语音
2019/11/22 Javascript
解决vue scoped scss 无效的问题
2020/09/04 Javascript
Ant Design Pro 之 ProTable使用操作
2020/10/31 Javascript
[01:46]DOTA2上海特锦赛小组赛英文解说KotlGuy采访
2016/02/27 DOTA
[00:37]2016完美“圣”典风云人物:AMS宣传片
2016/12/06 DOTA
[46:59]完美世界DOTA2联赛PWL S2 GXR vs Ink 第二场 11.19
2020/11/20 DOTA
python定向爬取淘宝商品价格
2018/02/27 Python
20行python代码的入门级小游戏的详解
2019/05/05 Python
python调用win32接口进行截图的示例
2020/11/11 Python
下述程序的作用是计算机数组中的最大元素值及其下标
2012/11/26 面试题
总经理工作职责范文
2014/03/14 职场文书
小学一年级学生评语
2014/04/22 职场文书
个人担保书范文
2014/05/20 职场文书
大学生翘课检讨书范文
2014/10/06 职场文书
2014年城管工作总结
2014/11/20 职场文书
2015庆祝七一建党节94周年活动总结
2015/03/20 职场文书
为什么说餐饮很难做,是因为你不了解这些新规则
2019/08/20 职场文书
Redis如何实现验证码发送 以及限制每日发送次数
2022/04/18 Redis