多次调用相同Async Await函数仅触发最后一次的问题排查
异步函数并发执行异常:原因分析与解决
问题代码
const fun = (x) => { console.log(`Meth... ${x}`); setTimeout(() => { onReceive(`Fun Received: ${x}`); }, 100); return x; } const publish = () => { return new Promise(resolve => { window.onReceive = (token) => { resolve(token); } }); } const Inst = () => { const getAs = async(x) => { console.log(`Getting As ----- ${x}`); const res = await publish(fun(x)); console.log(`Res is '${res}' ---- ${x}`); } return { getAs, } } const inst = Object.freeze(Inst()); const a = async() => { await inst.getAs(1); await inst.getAs(2); await inst.getAs(3); await inst.getAs(4); await inst.getAs(5); }; a(); const b = async() => { await inst.getAs(4); await inst.getAs(5); await inst.getAs(6); await inst.getAs(7); }; b(); // Comment or remove `b()` to see the different result in Console
现象
同时调用a()和b()时,仅b()中的inst.getAs()能正常执行,a()的代码完全停滞;注释或移除b()后,a()的所有inst.getAs()均可正常运行。
原因分析
核心问题在于全局变量window.onReceive被频繁覆盖:
- 每次调用
publish函数,都会把window.onReceive替换为当前Promise的resolve函数。 - 当
a()和b()同时启动,a()先调用getAs(1),给window.onReceive赋值了对应Promise的resolve;但紧接着b()调用getAs(4),直接把window.onReceive换成了自己的resolve。 - 100ms后,
fun(1)的setTimeout触发,调用的是被b()覆盖后的onReceive,这会resolveb()中getAs(4)的Promise,让b()的流程继续;而a()中getAs(1)的Promise永远处于pending状态,后续的await全部卡住,导致a()的代码无法继续执行。
解决方法
方案1:直接绑定Promise的resolve到当前调用(推荐)
改造fun和publish,让它们通过局部回调传递,不再依赖全局变量:
const fun = (x, callback) => { console.log(`Meth... ${x}`); setTimeout(() => { callback(`Fun Received: ${x}`); }, 100); return x; } const publish = (x) => { return new Promise(resolve => { fun(x, resolve); // 将当前Promise的resolve作为回调传给fun }); } const Inst = () => { const getAs = async(x) => { console.log(`Getting As ----- ${x}`); const res = await publish(x); console.log(`Res is '${res}' ---- ${x}`); } return { getAs, } } const inst = Object.freeze(Inst()); const a = async() => { await inst.getAs(1); await inst.getAs(2); await inst.getAs(3); await inst.getAs(4); await inst.getAs(5); }; a(); const b = async() => { await inst.getAs(4); await inst.getAs(5); await inst.getAs(6); await inst.getAs(7); }; b();
方案2:用唯一标识符管理回调(适合无法修改fun的场景)
如果不能修改fun的结构,可通过Map存储多个回调,避免全局覆盖:
const callbackMap = new Map(); let callbackId = 0; const fun = (x) => { console.log(`Meth... ${x}`); const id = callbackId++; setTimeout(() => { const callback = callbackMap.get(id); if (callback) { callback(`Fun Received: ${x}`); callbackMap.delete(id); // 执行后清理,防止内存泄漏 } }, 100); return id; // 返回唯一ID,用于绑定回调 } const publish = () => { return new Promise(resolve => { const id = fun(); callbackMap.set(id, resolve); // 将当前resolve与ID绑定 }); } // 以下Inst、a、b函数保持原代码不变 const Inst = () => { const getAs = async(x) => { console.log(`Getting As ----- ${x}`); const res = await publish(fun(x)); console.log(`Res is '${res}' ---- ${x}`); } return { getAs, } } const inst = Object.freeze(Inst()); const a = async() => { await inst.getAs(1); await inst.getAs(2); await inst.getAs(3); await inst.getAs(4); await inst.getAs(5); }; a(); const b = async() => { await inst.getAs(4); await inst.getAs(5); await inst.getAs(6); await inst.getAs(7); }; b();
内容的提问来源于stack exchange,提问作者Ferry
相关产品推荐
相关产品推荐

