Node.js Design Patterns 的作者建议将此代码作为一个操作示例,该操作从某个 queue
数组调度异步任务,并同时将许多正在运行的异步任务保持在限制以下 (concurrency
)(我让作者的例子更简单):
next () {
while (running < concurrency && queue.length) {
const task = queue.shift()
task(() => {
running--
process.nextTick(next)
})
running++
}
}
我的误解是为什么要跟踪通过 next()
(异步)调度的 process.nextTick()
调用?为什么任务回调不能立即运行next()
(即同步)?
作者指出,在 Node.js 世界中,当任务需要回调作为参数时,它始终应该异步运行它以避免严重的影响。但这是另一回事,箭头函数不会假装是一个异步任务,它只是一个回调本身。那么,异步任务回调应该以异步方式安排后续任务还是完全取决于风格,是否有一些令人信服的理由?
答案 0 :(得分:0)
这里的总体规则是:“如果回调通过任何代码路径被异步调用,那么它应该始终被异步调用,即使结果或错误是同步已知的。”强>
这是因为您永远不想要一个有时同步调用其回调有时异步调用的 API。这使得调用者很容易最终难以找到和重现错误,这些错误只有在 API 本身有时同步调用事物或有时异步调用它们或有时第一次以一种方式调用,然后第二次以另一种方式调用时才会出现.
这是我在 nodejs 流代码中看到的一个示例。流代码中有一个接受异步回调的 API。但是,当 API 开始处理事物时,它发现它的缓冲区中已经有满足 API 请求所需的数据。它可以同步调用回调,因为它已经有了数据。但是,其他时候,它必须在调用回调之前从磁盘读取数据,因此回调将被异步调用。在streams代码中,当遇到这种已经同步有数据的情况时,它仍然将回调排队等待nextTick调用,因为它需要始终只异步返回数据。
同样,有时,作为特定流 API 的设置代码的一部分,它会遇到错误并且该错误是同步已知的。同样,它只在 nextTick 上调用带有错误的回调,以便再次保持一致并始终异步调用回调。
但是,不要太过分,因为这并不意味着每个回调都应该只在未来的滴答声中异步调用。只要 API 解释了这一点并且调用者期望它始终被同步调用,那么回调就完全可以了。哎呀,像 .map()
或 .filter()
之类的所有数组迭代函数都可以做到这一点,并且可以很好地完成它们的工作,而且由于它们的同步性质,它们使用起来非常简单。
在您展示的特定代码的上下文中,执行以下操作:
process.nextTick(next)
而不仅仅是
next()
防止您获得任何堆栈构建,因为对 next()
的每次调用都将在其自己的干净堆栈框架上运行,即使该任务的回调是同步调用的。所以,一个总是异步调用它的行为良好的 task
不需要这个,但这是防止任务不那样做。