当您有异步调用时,例如。 async.each
,并且在该运行时,传入的数组对其进行了修改,然后它可能永远不会完成,或者将调用两次回调。
例子:
async = require "async"
arr = [1, 2, 3]
async.each arr, ((i, cb) -> console.log "i"; setImmediate(cb)), (err) -> console.log "done"
arr.push(4)
此示例循环遍历 3 个原始数组元素,并打印i
但从不调用回调,因为在async.each
它正在执行:
if (completed >= arr.length) {
callback(null);
}
在查看异步代码时,它正在与arr.length
进行比较,这可能会改变......存储原始数组长度并与之进行比较是否更好,确保将调用完成的回调?
你是对的——所以不要修改它。 如果您稍后需要更改它,请在将其传递给异步函数之前克隆您的数组。
@aearly是的,这是一种解决方法,但这里的潜在问题是,这是一个很容易犯的错误,并且是一个很难调试的问题,因此恕我直言,它应该在核心库中修复。 这让我在两个不同的项目上咬了两次。
在不克隆输入的情况下很难防止 _any_ 数组修改,例如迭代器函数中的同步修改,但是即使没有克隆,防止与迭代无关的 _asynchronous_ 修改也应该很简单——实际上只是不假设arr.length
将在启动异步作业后保持不变(在迭代之前将长度保存在变量中)。
(我与@bradens 合作——如果欢迎的话,我们中的一个人将提交带有测试的 PR)。
欢迎使用带有测试的 PR,但最终要由@caolan来合并它。
这样做的缺点是数组复制的额外开销。 如果你有一个大数组,或者多次调用async.each
等,它会变慢并使用更多的内存。
我同意数组副本的开销太大。 我建议由于数组的初始迭代是同步的,因此可以保存原始数组长度以供以后检查。 这样,回调调用的次数将与迭代器调用的次数相匹配,即使在此期间更改了数组。 对于async.each
,这应该很容易并且零开销,但我还没有研究其他函数。 如果所有并行函数在这些情况下都具有一致的行为,那就太好了。
这是#557 的副本。
事后异步最终是否禁止数组修改? 我有一个用例,其中最好在事后修改原始数组,以便迭代比最初更多的元素。
最有用的评论
事后异步最终是否禁止数组修改? 我有一个用例,其中最好在事后修改原始数组,以便迭代比最初更多的元素。