- Welcome to the Node.js Platform
- Node.js Essential Patterns
- Asynchronous Control Flow Patterns with Callbacks
- Asynchronous Control Flow Patterns with ES2015 and Beyond
- Coding with Streams
- Design Patterns
- Writing Modules
- Advanced Asynchronous Recipes
- Scalability and Architectural Patterns
- Messaging and Integration Patterns
- Welcome to the Node.js Platform
- Node.js 的发展
- Node.js 的特点
- 介绍 Node.js 6 和 ES2015 的新语法
- reactor 模式
- Node.js Essential Patterns
- Asynchronous Control Flow Patterns with Callbacks
- Asynchronous Control Flow Patterns with ES2015 and Beyond
- Coding with Streams
- Design Patterns
- Writing Modules
- Advanced Asynchronous Recipes
- Scalability and Architectural Patterns
- Messaging and Integration Patterns
async 库
如果我们到目前为止我们分析的每一个控制流程模式看一下,我们可以看到它们可以用作构建可重用和更通用的解决方案的基础。例如,我们可以将无限制的并行执行算法包装到一个接受任务列表的函数中,并行运行它们,并且当它们都完成时调用给定的回调函数。将控制流算法转化为可重用功能的这种方式可以导致更具声明性和表达性的方式来定义异步控制流,这正是 async 所做的。 async
库是一个非常流行的解决方案,在 Node.js
和 JavaScript
中来说,用于处理异步代码。它提供了一组功能,可以大大简化不同配置中一组任务的执行,并为异步处理集合提供了有用的帮助。即使有其他几个具有相似目标的库,由于它的受欢迎程度,因此 async
是 Node.js
中的一个事实上的标准。
顺序执行
async
库可以在实现复杂的异步控制流程时大大帮助我们,但是一个难题就是选择正确的库来解决问题。例如,对于顺序执行,有大约 20 个不同的函数可供选择,包括 eachSeries()
, mapSeries()
, filterSeries()
, rejectSeries()
, reduce()
, reduceRight()
, detectSeries()
, concatSeries()
, series()
, whilst()
, doWhilst()
, until()
, doUntil()
, forever()
, waterfall()
, compose()
, seq()
, applyEachSeries()
, iterator()
, 和 timesSeries()
。
选择正确的函数是编写更稳固和可读的代码的重要一步,但这也需要一些经验和实践。在我们的例子中,我们将仅介绍其中的一些情况,但它们仍将为理解和有效地使用库的其余部分提供坚实的基础。
下面,通过例子说明 async
库如何工作,我们将用于我们的 Web 爬虫
应用程序。我们直接从版本 2 开始,按顺序递归地下载所有的链接。
但是,首先我们确保将 async
库安装到我们当前的项目中:
npm install async
然后我们需要从 spider.js
模块加载新的依赖项:
const async = require('async');
已知一组任务的顺序执行
我们先修改 download()
函数。如下所示,它依次做了以下三件事:
- 下载
URL
的内容。 - 创建一个新目录(如果尚不存在)。
- 将
URL
的内容保存到文件中。
async.series()
可以实现顺序执行一组任务:
async.series(tasks, [callback])
async.series()
接受一个任务列表和一个在所有任务完成后调用的回调函数作为参数。每个任务只是一个接受回调函数的函数,当任务完成执行时,这个回调函数被调用:
function task(callback) {}
async
的优势是它使用与 Node.js
相同的回调约定,它会自动处理错误传播。所以,如果任何一个任务调用它的回调并且产生了一个错误, async
将跳过列表中剩余的任务,直接跳转到最后的回调。
考虑到这一点,让我们看看如何通过使用 async
来修改上述的 download()
函数:
function download(url, filename, callback) {
console.log(`Downloading ${url}`);
let body;
async.series([
callback => {
request(url, (err, response, resBody) => {
if (err) {
return callback(err);
}
body = resBody;
callback();
});
},
mkdirp.bind(null, path.dirname(filename)),
callback => {
fs.writeFile(filename, body, callback);
}
], err => {
if (err) {
return callback(err);
}
console.log(`Downloaded and saved: ${url}`);
callback(null, body);
});
}
对比起这段代码的回调地狱版本,使用 async
方式使我们能够更好地组织我们的异步任务。并且不会嵌套回调,因为我们只需要提供一个的任务列表,通常对于用于每个异步操作,然后异步任务将依次执行:
- 首先是下载
URL
的内容。我们将响应体保存到一个闭包变量(body
)中,以便它可以与其他任务共享。 - 创建并保存下载的页面的目录。我们通过执行
mkdirp()
函数实现,并和创建的目录路径绑定。这样,我们可以节省几行代码并增加其可读性。 - 最后,我们将下载的
URL
的内容写入文件。在这种情况下,我们无法执行部分应用程序(就像我们在第二个任务中所做的那样),因为变量body
只在系列中的下载任务完成后才可用。但是,通过将任务的回调直接传递到fs.writeFile()
函数,我们仍然可以通过利用异步的自动错误管理来保存一些代码行。 4.完成所有任务后,将调用async.series()
的最后回调。在我们的例子中,我们只是做一些错误管理,然后返回body
变量来回调download()
函数。
对于上述情况, async.series()
的一个可替代的方法是 async.waterfall()
,它仍然按顺序执行任务,但另外还提供每个任务的输出作为下一个输入。在我们的情况下,我们可以使用这个特征来传播 body
变量直到序列结束。
顺序迭代
在前面讲了如何按顺序执行一组任务。上面的例子 async.series()
来做到这一点。可以使用相同的功能来实现 Web 爬虫版本 2
的 spiderLinks()
函数。然而, async
为特定的情况提供了一个更合适的 API
,遍历一个集合,这个 API
是 async.eachSeries()
。我们来使用它来重新实现我们的 spiderLinks()
函数(版本 2,串行下载),如下所示:
function spiderLinks(currentUrl, body, nesting, callback) {
if (nesting === 0) {
return process.nextTick(callback);
}
const links = utilities.getPageLinks(currentUrl, body);
if (links.length === 0) {
return process.nextTick(callback);
}
async.eachSeries(links, (link, callback) => {
spider(link, nesting - 1, callback);
}, callback);
}
如果我们将使用 async
的上述代码与使用纯 JavaScript
模式实现的相同功能的代码进行比较,我们将注意到 async
在代码组织和可读性方面给我们带来的巨大优势。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论