如何实现队列为空时支持同步执行的有序Promise任务队列?
解决方案
原有实现的问题点
你之前用Promise.all等待Promise数组的思路有两个核心缺陷:
- 等待期间新增的Promise不会被纳入当前的Promise.all,会导致后续任务比前面的Promise先执行,顺序错乱
- 没有合适的时机清空数组:如果在Promise.all的回调里清空,可能会把等待期间新增的未处理Promise删掉;如果不清空,数组会越来越大,isEmpty判断永远返回false,后续所有任务都会被强制变成异步
核心设计思路
完全兼顾你需要的两个核心特性:
- 队列为空且没有处理中的异步任务时,同步任务(字符串)直接执行,完全不涉及Promise异步逻辑,保证原有单元测试100%兼容
- 队列存在未完成的异步任务时,所有后续任务(不管同步还是异步)严格按调用顺序排队,全部执行完成后自动重置队列为空状态,后续任务恢复同步执行特性
- 通用设计,和echo的具体实现完全解耦,可用于任意类似的任务调度场景
完整实现代码
class AsyncTaskQueue { constructor() { // 存储待处理任务 this._pendingTasks = []; // 标记是否正在处理异步任务 this._isProcessing = false; // 队列尾部Promise,用于保证任务执行顺序 this._tailPromise = Promise.resolve(); } /** * 添加任务到队列 * @param {*} task 任务内容,支持普通值、Promise * @param {Function} resolveCallback 任务完成后的回调,接收任务返回值 */ add(task, resolveCallback) { // 队列为空+无处理中任务+当前是同步值:直接同步执行,完全不碰Promise逻辑 if (this._pendingTasks.length === 0 && !this._isProcessing && typeof task?.then !== 'function') { resolveCallback(task); return; } // 否则加入待处理队列 const taskItem = typeof task?.then === 'function' ? { type: 'async', value: task, callback: resolveCallback } : { type: 'sync', value: task, callback: resolveCallback }; this._pendingTasks.push(taskItem); // 没有正在运行的处理流程就启动处理 if (!this._isProcessing) { this._processQueue(); } } /** * 检查队列是否完全空闲 * @returns {boolean} */ isIdle() { return this._pendingTasks.length === 0 && !this._isProcessing; } /** * 内部方法:按顺序处理队列中所有任务 */ _processQueue() { this._isProcessing = true; // 拷贝当前所有待处理任务,清空原队列接收后续新增任务 const tasksToProcess = [...this._pendingTasks]; this._pendingTasks = []; // 逐个将任务挂到尾部Promise后,保证严格按调用顺序执行 for (const task of tasksToProcess) { this._tailPromise = this._tailPromise.then(() => { if (task.type === 'sync') { task.callback(task.value); return Promise.resolve(); } return task.value.then(result => { task.callback(result); }); }); } // 当前批次任务处理完成后检查是否有新增任务 this._tailPromise = this._tailPromise.then(() => { if (this._pendingTasks.length > 0) { // 有新增任务,继续处理 this._processQueue(); } else { // 无新增任务,重置队列状态,恢复同步执行能力 this._isProcessing = false; this._tailPromise = Promise.resolve(); } }); } }
与echo方法集成示例
只需要在Terminal实例中初始化队列,调用时把渲染逻辑作为回调传入即可:
class Terminal { constructor() { this._taskQueue = new AsyncTaskQueue(); } echo(input) { // 处理传入函数的场景:执行函数拿到返回值 const task = typeof input === 'function' ? input() : input; // 加入队列,回调复用你原有的渲染逻辑 this._taskQueue.add(task, (outputText) => { this._renderToTerminal(outputText); }); } // 原有渲染逻辑,不需要修改 _renderToTerminal(text) { const el = document.querySelector('.terminal-output'); el.innerHTML += `<div class="line">${text}</div>`; } }
场景验证
场景1:混合Promise与字符串调用
你给出的第一个测试用例执行逻辑完全符合预期:
- 前6个echo按调用顺序排队执行,总耗时约3s全部渲染完成,之后队列自动重置为空闲状态
- 5s时调用
term.echo('lorem ipsum')时队列为空闲,直接同步渲染到DOM,单元测试可以立即查询到对应的DOM节点,没有任何异步延迟
场景2:全同步字符串调用
所有echo调用时队列都处于空闲状态,全部直接同步执行渲染,和原有实现行为完全一致,没有任何破坏性变更。
内容的提问来源于stack exchange,提问作者jcubic
相关产品推荐
相关产品推荐

