es6模块天然单例特性实现异步任务队列模块级单例:顶层创建唯一实例并导出接口,确保所有导入者共享同一队列状态,避免重复初始化与并发冲突,兼顾封装性、一致性与可扩展性。

用 export 实现异步任务队列的“模块级单例”,核心不是靠语法魔法,而是靠 ES6 模块的**天然单例特性**——每个模块在应用中只被加载执行一次,其顶层声明的变量、类实例或对象,在整个生命周期内就是唯一的。结合 export 控制暴露接口,就能安全封装一个可复用、可管理、带状态的异步队列。
利用模块顶层作用域创建唯一队列实例
ES6 模块默认具备单例语义:无论多少个文件 import 同一个模块,都共享同一份顶层代码执行结果。因此,直接在模块顶层 new 一个队列实例,并 export 出去,就天然实现了单例:
- 避免重复初始化(如多次 new AsyncQueue())
- 所有 import 者操作的是同一个队列,便于统一调度、暂停、清空
- 无需手动加锁或判断是否已存在实例
示例(task-queue.ts):
const queue = new AsyncQueue(); // 仅执行一次
export default queue;
按需导出控制能力,而非暴露内部状态
不直接 export default queue 就完事,而应通过 export 显式提供受控的操作接口,隐藏实现细节和内部状态(如任务数组、运行标志等),防止外部误操作破坏一致性:
export const addTask = (fn: () => Promise<any>) => queue.push(fn)</any>export const start = () => queue.start()export const pause = () => queue.pause()export const status = () => ({ size: queue.size, running: queue.isRunning })
这样使用者只能通过你定义的契约操作队列,既保障了封装性,又保持了模块单例的全局一致性。
配合默认导出 + 命名导出,兼顾简洁与扩展性
推荐组合使用:export default 暴露主实例(方便快速导入),再用 export 命名导出辅助方法或配置项:
-
import taskQueue from './task-queue'—— 直接用实例 -
import taskQueue, { addTask, clearAll } from './task-queue'—— 需要精细控制时引入命名导出 - 若未来需支持多队列场景,可额外
export const createQueue = () => new AsyncQueue(),但默认导出仍保持单例不变
注意与异步逻辑的协同设计
模块级单例对异步队列特别重要,因为很多状态(如“正在执行中”、“当前任务索引”、“失败重试计数”)必须跨调用共享。确保队列类本身是状态安全的:
- 内部使用
Promise链或async/await顺序执行,避免并发冲突 - 任务添加时做防抖/节流(如
addTask内部检查是否已启动) - 导出的
status或onIdle回调应基于微任务(Promise.resolve().then)通知,保证时机准确
这样封装出来的模块,既是轻量的(无依赖、无副作用),又是可靠的(状态唯一、接口可控),真正做到了“一次定义,处处复用”。











