推荐阅读
逛 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 许可证】。
对于转载作品,如需二次转载,请遵循原作许可。