如何实现长耗时函数后续调用需等待前次执行完毕再运行?
长耗时异步函数串行排队执行方案
最简正确实现
不需要引入第三方工具库,直接利用Promise链维护执行队列即可,无竞态问题,代码如下:
// 初始化队列起点,初始状态为已完成 let taskQueue = Promise.resolve(); /** * 包装后的排队执行函数 * 调用后无需主动await,任务会自动按调用顺序串行执行 */ function enqueueTask(...args) { // 将当前任务挂到队列末尾,前序任务执行完成(无论成功/失败)后自动启动当前任务 taskQueue = taskQueue.finally(() => longRunningAsyncFunc(...args)); // 若后续需要单独获取某次任务的结果/捕获错误,可直接return该Promise,无该需求可省略返回值 return taskQueue; }
方案逻辑说明
- 完全匹配需求:所有调用严格遵循调用顺序串行执行,前一个任务未执行完毕时,后续调用不会启动,不会出现多实例并发运行的情况
- 无竞态隐患:不需要手动维护
isRunning类的布尔状态锁,Promise原生的状态流转机制天然保证执行顺序,高频调用下也不会出现锁判断失效的问题 - 错误处理灵活:使用
finally挂载任务时,前序任务抛出异常不会阻塞后续排队任务;如果需要前序任务失败就终止整个队列,将finally替换为then即可
避坑提示
不要使用手动布尔锁的实现方式,类似下面的写法在高频调用场景下会出现并发问题:
// 错误实现,请勿使用 let isRunning = false; async function wrongImpl() { if (isRunning) return; isRunning = true; try { await longRunningAsyncFunc(); } finally { isRunning = false; } }
该写法的问题在于:前一个任务执行完毕将isRunning置为false的瞬间,多个积压的等待调用会同时通过条件判断进入执行阶段,仍然会产生并发。
效果验证
用1秒延时模拟长耗时任务做测试:
// 模拟长耗时异步函数 async function longRunningAsyncFunc(seq) { console.log('启动任务', seq); await new Promise(resolve => setTimeout(resolve, 1000)); console.log('结束任务', seq); } // 短时间内连续触发3次调用 enqueueTask(1); enqueueTask(2); enqueueTask(3);
执行输出顺序如下,总耗时3秒,无并发执行情况:
启动任务 1 (等待1秒) 结束任务 1 启动任务 2 (等待1秒) 结束任务 2 启动任务 3 (等待1秒) 结束任务 3
内容的提问来源于stack exchange,提问作者DTXqueque
相关产品推荐
相关产品推荐

