10月阅读周·JavaScript异步编程设计快速响应的网络应用:异步工作流的动态排队技术
背景
去年下半年,我在微信书架里加入了许多技术书籍,各种类别的都有,断断续续的读了一部分。
没有计划的阅读,收效甚微。
新年伊始,我准备尝试一下其他方式,比如阅读周。每月抽出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畅想》等系列作者。华夏美食、国漫、古风重度爱好者,刑侦、无限流小说初级玩家。
如果看完文章有所收获,欢迎点赞👍 | 收藏⭐️ | 留言📝。
- 点赞
- 收藏
- 关注作者
评论(0)