10月阅读周·JavaScript异步编程设计快速响应的网络应用:Async.js篇

举报
叶一一 发表于 2024/10/22 09:44:34 2024/10/22
【摘要】 背景去年下半年,我在微信书架里加入了许多技术书籍,各种类别的都有,断断续续的读了一部分。没有计划的阅读,收效甚微。新年伊始,我准备尝试一下其他方式,比如阅读周。每月抽出1~2个非连续周,完整阅读一本书籍。这个“玩法”虽然常见且板正,但是有效,已经坚持阅读九个月。已读完书籍:《架构简洁之道》、《深入浅出的Node.js》、《你不知道的JavaScript(上卷)》、《你不知道的JavaScri...

背景

去年下半年,我在微信书架里加入了许多技术书籍,各种类别的都有,断断续续的读了一部分。

没有计划的阅读,收效甚微。

新年伊始,我准备尝试一下其他方式,比如阅读周。每月抽出1~2个非连续周,完整阅读一本书籍。

这个“玩法”虽然常见且板正,但是有效,已经坚持阅读九个月。

已读完书籍《架构简洁之道》、《深入浅出的Node.js》、《你不知道的JavaScript(上卷)》、《你不知道的JavaScript(中卷)》、《你不知道的JavaScript(下卷)》、《数据结构与算法JavaScript描述》、《WebKit技术内幕》、《前端架构:从入门到微前端》、《秒懂算法:用常识解读数据结构与算法》、《JavaScript权威指南》

当前阅读周书籍《JavaScript异步编程设计快速响应的网络应用》

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的错误处理技术

简单来说,Async.js遵守Node的约定。这意味着所有的I/O回调都形如callback(err, results……),唯一的例外是结果为布尔型的回调。布尔型回调的写法就是callback(result),所以上一代码示例中的isFilename迭代器需要自己亲自处理错误。

Asyncjs/forEachSeries.js

function isFilename(filename, callback) {
  fs.stat(filename, function (err, stats) {
    if (err) throw err;
    callback(stats.isFile());
  });
}

要怪就怪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只被调用一次,而不管是因首次出错而调用,还是因成功完成所有操作而调用。

总结

Node的错误处理约定对 Async.js数据收集方法而言也许并不理想,但对于Async.js的6所有其他方法而言,遵守这些约定可以让错误干净利落地从各个任务流向完工回调。


作者介绍
非职业「传道授业解惑」的开发者叶一一。
《趣学前端》、《CSS畅想》等系列作者。华夏美食、国漫、古风重度爱好者,刑侦、无限流小说初级玩家。
如果看完文章有所收获,欢迎点赞👍 | 收藏️ | 留言📝

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。