10月阅读周·JavaScript异步编程设计快速响应的网络应用:异步工作流的动态排队技术

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

背景

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

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

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

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

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

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

异步工作流的动态排队技术

大多数情况下,前两节介绍的那些简单方法足以解决我们的异步窘境,但async.series和async.parallel均存在各自的局限性。

  • 任务列表是静态的。一旦调用了async.series或async.parallel,就再也不能增减任务了。
  • 不可能问:“已经完成多少任务了?”任务处于黑箱状态,除非我们自行从任务内部派发更新信息。
  • 只有两个选择,要么是完全没有并发性,要么是不受限制的并发性。这对文件I/O任务可是个大问题。如果要操作上千个文件,当然不想因按顺序操作而效率低下,但如果试着并行执行所有操作,又很可能会激怒操作系统。

Async.js提供了一种可以解决上述所有问题的全能方法:async.queue。

深入理解队列

async.queue 的底层基本理念令人想起DMV(Dynamic Management View,动态管理视图)。它可以同时应对很多人(最多时等于在岗办事员的数目),但并不是每位办事员前面各排一个队,而是维持着一个排号队列。人到了就排队,并取得一个排队号码。任何一个办事员空闲时,就会叫下一个排队号码。

async.queue的接口比async.series和async.parallel稍微复杂一些。async.queue接受的参数有两个:一个是worker(办事员)函数,而不是一个函数列表;一个是代表着concurrency(并发度)的值,代表了办事员最多可同时处理的任务数。async.queue 的返回值是一个队列,我们可以向这个队列推入任意的任务数据及可选的回调。

下面是一个小例子。

Asyncjs/simpleQueue.js

var async = require('async');
function worker(data, callback) {
  console.log(data);
  callback();
}
var concurrency = 2;
var queue = async.queue(worker, concurrency);
queue.push(1);
queue.push(2);
queue.push(3);

不论并发度是多少(只要不小于1),都会得到以下输出。

1
2
3

不过内在还是有点小区别:并发度为2时,需要两轮才能遍历事件队列;如果并发度为1,则需要3轮才能遍历,每轮输出一行代码;如果并发度为3或更大的值,则只需要1轮即可遍历。

并发度为0的队列不会做任何事情。如果想要最大的并发度,请直接使用Infinity关键字。

任务的入列

虽然queue.push与[].push同名,但二者存在两个很关键的差别。

第一个差别。请看这行代码:

queue.push([1, 2, 3]);

它等价于下面这3行代码:

queue.push(1);
queue.push(2);
queue.push(3);

这意味着不能直接使用数组作为任务的数据。不过可以使用其他任何东西(甚至函数)作为任务的数据。事实上,如果想让async.queue像 async.series/async.parallel 那样也使用一组函数作为任务列表,只需定义一个其次参数会直接传递给其首参数的worker函数。

function worker(task, callback) {
  task(callback);
}
var concurrency = 2;
var queue = async.queue(worker, concurrency);
queue.push(tasks);

第二个差别。async.queue中的每次push调用可附带提供一个回调函数。如果提供了,该回调函数会直接送给 worker 函数作为其回调参数。因此,(假设worker函数确实运行了其回调,即它未因抛出错误而直接关停)下面这个例子将会触发 3 次输出事件,即输出 3 次'Task complete!'。

queue.push([1, 2, 3], function (err, result) {
  console.log('Task complete!');
});

对async.queue而言,push方法的回调函数非常重要,因为async.queue不像async.series/async.parallel那样可以在内部存储每次任务的结果。如果想要这些结果,就必须自行去捕获。

队列的高级回调方法

尽管 drain 常常是我们唯一要用到的事件处理器,但 async.queue还是提供了其他一些事件及其处理器。

  • 队末任务开始运行时,会调用队列的empty 方法。(队末任务运行结束时,会调用队列的drain方法。)
  • 达到并发度的上限时,会调用队列的saturated 方法。
  • 如果提供了一个函数作为push 方法的次参数,则在结束运行给定任务时会调用该函数,或在给定任务列表中的每个任务结束运行时均调用一次该函数。

总结

如果需要在有限的并发度下运行大量的异步任务,请考虑使用async.queue。

Async.js是使用最广泛、当然也是功能最丰富的 JavaScript工作流控制库,针对它的讨论到此为止。不过,请勿以为对于所有基于回调驱动的任务来说,Async.js都是一种恰当的工具。


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

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

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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