JavaScript Promise.all并行场景下push正常concat丢失元素原因求解
问题核心原因
本质是异步并行任务的竞态条件 + 不可变操作的赋值非原子性共同导致的:Promise.all传入的多个异步回调是并行执行的,await之后的代码执行时机完全随机,两个操作会争抢共享变量employments的读写权。
版本1(push)正常的原因
Array.push()是对原数组直接修改的可变操作:
- 所有异步任务操作的都是同一个内存地址的数组,不管哪个任务先执行追加操作,都会在已有元素的基础上新增内容,不会出现数据覆盖丢失的情况。
版本2(concat)异常的原因
Array.concat()是不可变操作,不会修改原数组,而是返回全新的数组,你写的employments = employments.concat(xxx)实际包含三个步骤:
- 读取当前
employments的最新值 - 拼接新元素生成新数组
- 把新数组赋值给
employments变量
这三个步骤不是原子操作,多个并行任务很容易出现读写冲突,举个实际的冲突场景:
初始时
employments = []
任务1和任务2先后执行到await mergeWithOtherSource处暂停,都已经读取到employments的初始值为[]
任务2的await先返回,拼接后得到新数组[a,b]赋值给employments
任务1的await随后返回,用之前读取到的旧值[]拼接得到[c],直接覆盖了任务2刚赋值的结果
最终employments只剩下[c],数据丢失,这就是你遇到随机返回1-3个元素的原因。
修复方案
两种可靠实现:
- 继续使用版本1的push写法,逻辑最简单性能也最高
- 如果你需要严格的不可变操作,不要在并行任务中直接修改共享变量,先收集所有结果再一次性合并:
let companyIds = ['Id1', 'Id2'] // 先收集所有异步任务的返回结果 const employmentChunks = await Promise.all( companyIds.map( async ( companyId ) => { const companies = await getCompaniesWithId(companyId); return mergeWithOtherSource( companies ); } ) ); // 最后一次性合并所有结果 const employments = employmentChunks.flat(); return employments.length
内容的提问来源于stack exchange,提问作者Jeremias Nater
相关产品推荐
相关产品推荐

