Bluebird.each异步回调中操作外部数组与Record是否安全
问题解答
你代码中的两处填充操作都是安全的,不存在异步竞态导致的数据异常问题,原因如下:
- 首先是JavaScript单线程特性的保证:所有同步执行的内存修改操作(对象属性赋值
idsToData[elementId] = data、数组push方法)都是原子性执行的,不会出现两个操作同时篡改同一个数据结构的情况,也不会出现数据结构损坏的问题。 - 其次是你使用的
Bluebird.each本身是串行迭代逻辑:它会等待前一个迭代的异步函数执行完成(Promise resolve)后,才会启动下一个迭代的执行,同一时间最多只有一个迭代的逻辑在运行,连多个迭代同时修改数据的可能性都不存在。
额外注意事项
就算你后续换用支持并发的迭代方法(比如Bluebird.map设置concurrency > 1),只要满足下面两个条件,操作依然是安全的:
- 传入的
elementIds数组中没有重复的id值,不会出现多个迭代同时给idsToData的同一个属性赋值的情况(就算出现重复id,最多是后赋值的覆盖先赋值的,不会引发代码运行异常) - 不需要依赖数组
idsNeedingMoreThingsToBeDone的元素顺序和elementIds的顺序完全一致:并发场景下push的顺序会和迭代完成顺序一致,和原始id顺序可能不同。
注意你当前的代码存在两处语法/逻辑错误会导致功能不符合预期:
Redis.get是异步IO操作,你没有加await关键字,拿到的data永远是Promise对象,if(data)判断会永远为真- 你拼接缓存key用的是单引号,模板字符串语法需要用反引号包裹,应该改为
`cache/${elementId}`
修正后参考代码
async fun(elementIds: string[]): Promise<void> { const idsNeedingMoreThingsToBeDone = []; const idsToData: Record<string, number> = {}; await Bluebird.each(elementIds, async (elementId) => { // 补充await + 修正模板字符串引号 const data = await Redis.get(`cache/${elementId}`); if(data) { idsToData[elementId] = data; } else { idsNeedingMoreThingsToBeDone.push(elementId); } }); }
内容的提问来源于stack exchange,提问作者ProgrammingRookie
相关产品推荐
相关产品推荐

