1. 项目概述:可取消Promise队列的设计价值
在复杂的前端应用和Node.js服务中,我们经常遇到这样的场景:需要管理多个异步操作的执行顺序,同时还要处理可能的取消操作和资源回收。传统的Promise链式调用虽然解决了回调地狱问题,但在精细控制方面仍显不足。这就是为什么我们需要设计一个具备"可取消等待 + 单例 Promise + WeakRef 清理"特性的队列系统。
这个方案的核心价值在于:
- 可取消等待:允许开发者主动终止尚未完成的异步操作,避免不必要的资源消耗
- 单例Promise:确保同一资源的请求不会重复创建Promise实例
- WeakRef清理:利用现代JavaScript的弱引用特性自动回收不再需要的资源
这种设计特别适合以下场景:
- 文件上传/下载的批量管理
- 高频数据请求的节流控制
- 需要保证唯一性的资源加载
- 长时间运行任务的超时处理
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心技术组件解析
2.1 TypeScript类型系统设计
首先我们需要定义队列的核心类型接口。TypeScript的强大类型系统能帮助我们构建更健壮的队列实现:
typescript复制interface QueueItem<T = any> {
id: string | symbol;
promise: () => Promise<T>;
cancel?: (reason?: any) => void;
weakRef?: WeakRef<{ id: string | symbol }>;
}
interface QueueController {
cancel: (reason?: any) => void;
promise: Promise<any>;
}
这里的关键设计点:
- 使用泛型
<T>保持类型灵活性 id支持string和symbol两种类型,满足不同场景需求- 将promise定义为函数而非直接值,实现懒加载
- 通过WeakRef保持对队列项的弱引用
2.2 Promise单例化实现
避免重复创建相同Promise的核心方法是维护一个缓存映射:
typescript复制class PromiseQueue {
private cache = new Map<string | symbol, QueueController>();
add<T>(id: string | symbol, creator: () => Promise<T>): Promise<T> {
if (this.cache.has(id)) {
return this.cache.get(id)!.promise as Promise<T>;
}
let cancel!: (reason?: any) => void;
const promise = new Promise<T>((resolve, reject) => {
cancel = reject;
creator().then(resolve).catch(reject);
});
const controller = { promise, cancel };
this.cache.set(id, controller);
promise.finally(() => {
this.cache.delete(id);
});
return promise;
}
}
这种实现确保了:
- 相同id的请求返回同一个Promise实例
- Promise完成后自动清理缓存
- 提供了取消功能的基础支持
2.3 WeakRef资源清理机制
为了避免内存泄漏,我们需要在适当的时候清理不再需要的资源。WeakRef是ES2021引入的特性,非常适合这种场景:
typescript复制class PromiseQueue {
private weakMap = new WeakMap<object, QueueItem>();
addWithWeakRef<T>(item: QueueItem<T>): Promise<T> {
const ref = new WeakRef({ id: item.id });
this.weakMap.set(ref.deref()!, item);
return this.add(item.id, () => {
const target = ref.deref();
if (!target) {
return Promise.reject(new Error('Resource already collected'));
}
return item.promise();
});
}
}
WeakRef的使用注意事项:
- 必须配合
deref()方法检查引用是否还存在 - 适合管理大型对象或DOM元素等资源
- 不能过度依赖,GC时机由运行时决定
3. 完整队列实现与核心方法
3.1 队列基础架构
下面是整合了所有特性的完整队列实现:
typescript复制class AdvancedPromiseQueue {
private cache = new Map<string | symbol, QueueController>();
private weakRefs = new Set<WeakRef<object>>();
add<T>(item: QueueItem<T>): Promise<T> {
// 单例检查
if (this.cache.has(item.id)) {
return this.cache.get(item.id)!.promise as Promise<T>;
}
// 创建可取消的Promise
let cancel!: (reason?: any) => void;
const promise = new Promise<T>((resolve, reject) => {
cancel = (reason = 'Cancelled') => {
item.cancel?.(reason);
reject(new Error(reason));
};
Promise.resolve()
.then(item.promise)
.then(resolve)
.catch(reject);
});
// 设置WeakRef
if (item.weakRef) {
this.weakRefs.add(item.weakRef);
promise.finally(() => this.cleanupWeakRef(item.weakRef!));
}
// 缓存管理
const controller = { promise, cancel };
this.cache.set(item.id, controller);
promise.finally(() => {
this.cache.delete(item.id);
});
return promise;
}
cancel(id: string | symbol, reason?: any): boolean {
const controller = this.cache.get(id);
if (controller) {
controller.cancel(reason);
return true;
}
return false;
}
private cleanupWeakRef(ref: WeakRef<object>) {
this.weakRefs.delete(ref);
}
}
3.2 关键方法详解
add方法工作流程:
- 检查是否已存在相同id的Promise
- 创建可取消的新Promise
- 设置WeakRef跟踪(如果提供)
- 管理缓存生命周期
- 返回Promise实例
cancel方法特性:
- 支持自定义取消原因
- 返回布尔值表示是否取消成功
- 会触发Promise的reject和item.cancel回调
内存管理策略:
- 使用Set管理所有WeakRef
- 提供专门的清理方法
- Promise完成后自动解除引用
4. 实战应用与性能优化
4.1 文件上传队列案例
typescript复制const uploadQueue = new AdvancedPromiseQueue();
async function uploadFile(file: File): Promise<string> {
const id = Symbol(`file-${file.name}`);
return uploadQueue.add({
id,
async promise() {
const formData = new FormData();
formData.append('file', file);
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
if (!response.ok) throw new Error('Upload failed');
return response.json();
},
cancel() {
// 中止上传逻辑
console.log(`Upload cancelled: ${file.name}`);
}
});
}
// 取消上传示例
const upload = uploadFile(someFile);
setTimeout(() => uploadQueue.cancel(upload), 5000); // 5秒后取消
4.2 API请求节流方案
typescript复制const apiQueue = new AdvancedPromiseQueue();
function fetchUserData(userId: string): Promise<UserData> {
return apiQueue.add({
id: `user-${userId}`,
async promise() {
const response = await fetch(`/api/users/${userId}`);
return response.json();
}
});
}
// 同一用户数据的多次请求只会实际发送一次
fetchUserData('123');
fetchUserData('123'); // 返回同一个Promise
4.3 性能优化技巧
- 缓存策略优化:
- 限制缓存最大数量
- 实现LRU淘汰机制
- 添加TTL自动过期
typescript复制class OptimizedPromiseQueue extends AdvancedPromiseQueue {
private maxSize = 100;
add<T>(item: QueueItem<T>): Promise<T> {
if (this.cache.size >= this.maxSize) {
const [oldestId] = this.cache.keys();
this.cancel(oldestId, 'Cache overflow');
}
return super.add(item);
}
}
- WeakRef使用最佳实践:
- 不要过度依赖WeakRef进行关键业务逻辑
- 配合FinalizationRegistry实现更可靠的清理
- 定期检查WeakRef状态
typescript复制const registry = new FinalizationRegistry((id) => {
console.log(`Resource ${id} was collected`);
});
function trackResource(obj: object, id: string) {
registry.register(obj, id);
}
5. 常见问题与调试技巧
5.1 典型问题排查表
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 取消操作无效 | 1. id不匹配 2. Promise已解决 |
1. 检查id生成逻辑 2. 添加状态检查 |
| 内存泄漏 | 1. 缓存未清理 2. 循环引用 |
1. 实现缓存限制 2. 使用WeakRef |
| 类型错误 | 1. 泛型使用不当 2. 类型断言错误 |
1. 检查类型定义 2. 减少any使用 |
5.2 调试技巧
- 添加日志追踪:
typescript复制class LoggedPromiseQueue extends AdvancedPromiseQueue {
add<T>(item: QueueItem<T>): Promise<T> {
console.log('[Queue] Adding:', item.id);
const promise = super.add(item);
promise
.then(() => console.log('[Queue] Resolved:', item.id))
.catch((e) => console.log('[Queue] Rejected:', item.id, e));
return promise;
}
}
- 性能监控:
typescript复制const perf = {
start: performance.now(),
counts: { added: 0, resolved: 0, cancelled: 0 }
};
// 在add/cancel方法中添加计数
perf.counts.added++;
- 测试策略建议:
- 验证单例行为
- 测试取消功能
- 内存泄漏测试
- 并发压力测试
5.3 浏览器兼容性处理
虽然WeakRef是现代特性,但我们可以实现渐进增强:
typescript复制function createWeakRef<T extends object>(obj: T): WeakRef<T> {
return window.WeakRef
? new WeakRef(obj)
: { deref: () => obj }; // 简单回退
}
对于不支持的环境,可以考虑:
- 使用Map+定时清理的polyfill
- 提供降级模式
- 提示用户升级浏览器
6. 高级应用与扩展思路
6.1 优先级队列实现
扩展基础队列,支持优先级调度:
typescript复制interface PriorityQueueItem<T> extends QueueItem<T> {
priority: number;
}
class PriorityPromiseQueue extends AdvancedPromiseQueue {
private priorityMap = new Map<string | symbol, number>();
add<T>(item: PriorityQueueItem<T>): Promise<T> {
this.priorityMap.set(item.id, item.priority);
return super.add(item);
}
// 重写执行顺序逻辑
// ...
}
6.2 超时自动取消
集成超时控制功能:
typescript复制class TimeoutPromiseQueue extends AdvancedPromiseQueue {
add<T>(item: QueueItem<T> & { timeout?: number }): Promise<T> {
const promise = super.add(item);
if (item.timeout) {
const timer = setTimeout(() => {
this.cancel(item.id, `Timeout after ${item.timeout}ms`);
}, item.timeout);
promise.finally(() => clearTimeout(timer));
}
return promise;
}
}
6.3 与RxJS集成
将队列转换为Observable:
typescript复制import { from, Observable } from 'rxjs';
function observableFromQueue<T>(
queue: AdvancedPromiseQueue,
item: QueueItem<T>
): Observable<T> {
return from(queue.add(item));
}
这种集成方式可以:
- 结合RxJS强大的操作符
- 实现更复杂的流控制
- 方便地组合多个队列操作
