推荐阅读
逛 B 站看到某前辈的视频领导感觉这个bug在针对我【渡一教育】,分析了一个 bug, 我感觉分析的不够深入,因此在这儿简单分享一下我的看法。
function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms))
}
/**
* 模拟异步请求
*/
async function fetchCount(n: number) {
await sleep(Math.random() * 1000)
return n
}
let count = 0
async function addCount(n: number) {
count = count + await fetchCount(n)
console.log(count)
}
async function main() {
// 如果此处这么调用,count 最终可能为 1 或 2,而不是 3
addCount(1)
addCount(2)
}
void main()
如果我们如上面 main
函数所述那么调用,则最终 count
可能为 1 或者 2,而不是我们期望的 3
前辈说,
“在一个表达式里面,同步数据跟异步数据进行混合运算,不并发就没问题,一并发就会出问题。”
我认为这样的解释比较牵强比较浅。
我在视频下面评论说,本质上是闭包问题。
我们按照时间顺序(代码执行顺序)来分析整个代码的执行:
第一轮,执行 addCount(1)
和 addCount(2)
, 此时 count
值为 0, 因此:
addCount(1)
中的 count = count + await fetchCount(n)
被解释为 count = 0 + await fetchCount(1)
addCount(2)
中的 count = count + await fetchCount(n)
被解释为 count = 0 + await fetchCount(2)
之后就等待 fetchCount
resolved
。
第二轮,我们假设 fetchCount(1)
先 resolved
, 此时我们观察上面可以发现 count = 0 + 1
即为 1
第三轮 此时 fetchCount(2)
resolved
, 此时我们观察上面可以发现 count = 0 + 2
即为 2
也就是说,由于 addCount(1)
和 addCount(2)
是同步执行的,在他们各自的闭包(上下文)中,count
的值已经提前被“缓存”了,等到他们各自的 fetchCount
resolved
的时候,count
的值已经“过期”了。
既然我们需要避免 count
被“提前”“缓存”,那么只需要把 count
放在 await fetchCount(x)
后面就行了,有以下 2 种方案可选:
将 count = count + await fetchCount(n)
改为 count = await fetchCount(n) + count
即可;
将 main
函数改为:
async function main() {
await addCount(1)
await addCount(2)
}
不让 addCount(1)
和 addCount(2)
同步执行,而是先执行 addCount(1)
, 得到结果,更新了 count 值之后,再执行 addCount(2)
, 这时候 addCount(2)
里面拿到的 count
就是最新的 count
了。
如非特别声明,本站作品均为原创,遵循【自由转载-保持署名-非商用-非衍生 创意共享 3.0 许可证】。
对于转载作品,如需二次转载,请遵循原作许可。