TS实现一个“可取消等待 + 单例 Promise + WeakRef 清理”队列

这个需求,本质上是 "Promise 去重 + 可取消等待" 的组合,而且要做到 高性能、无锁、无多余状态切换

一个 最优解模式:单例执行 + 等待者可取消 + 结果复用,并且是 TypeScript 最佳实践。


✅ 目标总结(我重新整理成技术语言)

有一个耗时异步函数 expensiveTask()

  • 同一时间只能执行一次
  • 后续重复调用如果发现正在执行 → 不再触发新的执行,而是等待当前执行的结果
  • 等待中的调用如果被取消 → 立即结束,不再等待,也不拿结果
  • 执行完成后,所有仍在等待的调用都拿到同一个结果(成功或失败)
  • 调用次数可能非常多 → 必须高性能

✅ 最佳实现:可取消等待 + 单例 Promise + WeakRef 清理

下面是最终推荐的实现:

ts 复制代码
type CancelToken = {
    cancelled: boolean;
};

export class SingletonAsync<T> {
    private currentPromise: Promise<T> | null = null;
    private waiters = new Set<{
        resolve: (v: T) => void;
        reject: (e: any) => void;
        token: CancelToken;
    }>();

    constructor(private readonly task: () => Promise<T>) {}

    run(token?: CancelToken): Promise<T> {
        const cancelToken = token ?? { cancelled: false };

        // 若已有执行中的任务 → 直接加入等待队列
        if (this.currentPromise) {
            return new Promise<T>((resolve, reject) => {
                if (cancelToken.cancelled) {
                    reject(new Error("Cancelled"));
                    return;
                }
                this.waiters.add({ resolve, reject, token: cancelToken });
            });
        }

        // 启动新的任务
        this.currentPromise = this.task()
            .then(result => {
                // 通知所有未取消的等待者
                for (const w of this.waiters) {
                    if (!w.token.cancelled) w.resolve(result);
                }
                return result;
            })
            .catch(err => {
                for (const w of this.waiters) {
                    if (!w.token.cancelled) w.reject(err);
                }
                throw err;
            })
            .finally(() => {
                // 清理
                this.waiters.clear();
                this.currentPromise = null;
            });

        return this.currentPromise;
    }
}

✅ 使用方式

ts 复制代码
const expensiveRunner = new SingletonAsync(async () => {
    console.log("真正执行一次任务...");
    await new Promise(r => setTimeout(r, 3000));
    return 42;
});

// 调用方
const token = { cancelled: false };

expensiveRunner.run(token).then(console.log).catch(console.error);

// 某处取消
token.cancelled = true;

✅ 行为完全符合你的需求

需求 是否满足 说明
1. 方法耗时 不影响
2. 多次调用只执行一次 currentPromise 保证单例执行
3. 等待中的调用可取消 token.cancelled = true 立即 reject
4. 高性能 无锁、无多余 promise、无事件循环压力

✅ 为什么这是性能最好的方案?

✔ 无重复执行

所有重复调用都复用同一个 Promise。

✔ 等待者是 Set,O(1) 插入删除

适合大量调用。

✔ 无轮询、无额外事件队列

只有真正执行一次任务。

✔ 可取消等待,不影响主任务

取消只影响当前调用,不会破坏正在执行的任务。

相关推荐
落魄江湖行1 天前
入门篇八 Nuxt4页面元信息与 SEO:让搜索引擎爱上你的网站
前端·typescript·seo·nuxt4
LcGero1 天前
TypeScript 快速上手:前言
typescript·cocos creator·游戏开发
条tiao条1 天前
TypeScript 网络编程从零到一:net 模块全解析(入门专属)
javascript·网络·typescript
ZHENGZJM1 天前
前端基石:React + Vite + TypeScript 项目搭建
前端·react.js·typescript
曲幽1 天前
告别手写 API 胶水代码:FastAPI 与 Vue 的“契约自动机” OpenAPI 实战
python·typescript·vue·fastapi·web·swagger·openapi·codegen
@二进制2 天前
vue3+vant4+ts出现页面空白?甚至在App.vue的<template></template>中随便输入都无法显示?
前端·vue.js·typescript
桂森滨2 天前
Vue3+Pinia+Vite+TS 还原高性能外卖APP项目 4️⃣首页开发
前端·typescript·vue
samroom2 天前
【鸿蒙应用开发 Dev ECO Studio 5.0版本】从0到1!从无到有!最全!计算器------按钮动画、滑动退格、中缀表达式转后缀表达式、UI设计
数据结构·ui·华为·typescript·harmonyos·鸿蒙
hong1616882 天前
TypeScript类型断言
linux·javascript·typescript
落魄江湖行2 天前
入门篇六 Nuxt4错误处理:给应用装个安全气囊
前端·typescript·nuxt4