@mt-kit/utils
Version:
276 lines • 9.57 kB
JavaScript
/**
* 消息队列
* 支持两种模式:
* 1. 串行模式:按顺序执行,一个完成后再执行下一个
* 2. 持续防抖模式:一段时间内有新请求就取消前面的,只保留最后一个,且只有在最后一次请求结束后的一段时间内没有新请求,才真正执行
*/
// 默认防抖时间(毫秒)
const DEFAULT_DEBOUNCE_TIME = 300;
// 存储各队列的待执行任务
const queues = new Map();
// 存储各队列正在执行的任务ID (用于取消正在执行的任务)
const runningTaskIds = new Map();
// 存储各队列的取消ID集合
const cancelledIds = new Map();
// 存储队列是否正在处理中
const processing = new Map();
// 存储防抖模式下的待执行任务 (key -> item)
const pendingDebounceItem = new Map();
/**
* 检查任务是否被取消
*/
const isCancelled = (key, id) => { var _a, _b; return (_b = (_a = cancelledIds.get(key)) === null || _a === void 0 ? void 0 : _a.has(id)) !== null && _b !== void 0 ? _b : false; };
/**
* 标记之前的任务为已取消
*/
const markPreviousAsCancelled = (key, currentId) => {
var _a, _b;
const cancelledSet = (_a = cancelledIds.get(key)) !== null && _a !== void 0 ? _a : new Set();
cancelledIds.set(key, cancelledSet);
// 1. 取消排队中的任务 (普通队列模式)
(_b = queues.get(key)) === null || _b === void 0 ? void 0 : _b.forEach(item => {
if (item.id !== currentId) {
cancelledSet.add(item.id);
}
});
// 2. 取消正在执行的任务
const runningId = runningTaskIds.get(key);
if (runningId && runningId !== currentId) {
cancelledSet.add(runningId);
}
// 3. 取消防抖等待中的任务
const pendingItem = pendingDebounceItem.get(key);
if (pendingItem && pendingItem.id !== currentId) {
cancelledSet.add(pendingItem.id);
if (pendingItem.timer) {
clearTimeout(pendingItem.timer);
}
// 立即 reject 旧任务
pendingItem.reject(new Error("任务已取消:新任务已到达"));
pendingDebounceItem.delete(key);
}
};
/**
* 清理已取消的任务ID(避免内存泄漏)
*/
const cleanupCancelledIds = (key) => {
const cancelledSet = cancelledIds.get(key);
if (!cancelledSet) {
return;
}
// 清理已不在队列中的取消ID
const queueList = queues.get(key);
const runningId = runningTaskIds.get(key);
const pendingItem = pendingDebounceItem.get(key);
const activeIds = new Set();
if (queueList) {
queueList.forEach(item => activeIds.add(item.id));
}
if (runningId) {
activeIds.add(runningId);
}
if (pendingItem) {
activeIds.add(pendingItem.id);
}
// 只保留活跃的取消ID,清理不再需要的
if (cancelledSet.size > activeIds.size * 2) {
// 如果取消集合太大,清理所有不在活跃列表中的ID
const toDelete = [];
cancelledSet.forEach(id => {
if (!activeIds.has(id)) {
toDelete.push(id);
}
});
toDelete.forEach(id => cancelledSet.delete(id));
}
};
/**
* 检查队列是否空闲,如果空闲则清理相关资源
*/
const checkAndCleanQueue = (key) => {
const queueList = queues.get(key);
const isQueueEmpty = !queueList || queueList.length === 0;
const isRunning = runningTaskIds.has(key);
const isPendingDebounce = pendingDebounceItem.has(key);
// 如果既没有排队的任务,也没有正在运行的任务,也没有正在防抖等待的任务,则清理资源
if (isQueueEmpty && !isRunning && !isPendingDebounce) {
queues.delete(key);
cancelledIds.delete(key);
processing.delete(key);
runningTaskIds.delete(key);
pendingDebounceItem.delete(key);
}
else {
// 队列还在使用中,清理已取消的ID避免内存泄漏
cleanupCancelledIds(key);
}
};
/**
* 处理普通队列
*/
const processQueue = async (key) => {
// 如果正在处理,直接返回
if (processing.get(key)) {
return;
}
const queueList = queues.get(key);
if (!queueList || queueList.length === 0) {
processing.set(key, false);
runningTaskIds.delete(key);
checkAndCleanQueue(key);
return;
}
// 开始处理队列
processing.set(key, true);
while (queueList.length > 0) {
const item = queueList.shift();
if (!item) {
break;
}
// 记录正在执行的任务ID
runningTaskIds.set(key, item.id);
// 检查是否被取消 (执行前)
if (isCancelled(key, item.id)) {
item.reject(new Error("任务已取消:前置任务已被取消"));
continue;
}
try {
// eslint-disable-next-line no-await-in-loop
const result = await item.task();
// 检查是否被取消 (执行后)
if (isCancelled(key, item.id)) {
item.reject(new Error("任务已取消"));
}
else {
item.resolve(result);
}
}
catch (error) {
// 检查是否被取消 (出错后)
if (!isCancelled(key, item.id)) {
item.reject(error);
}
// 如果被取消,静默忽略错误
}
}
// 处理完成,清理状态
runningTaskIds.delete(key);
processing.set(key, false);
checkAndCleanQueue(key);
};
/**
* 执行单个任务 (用于防抖模式)
*/
const executeTask = async (key, item) => {
var _a;
runningTaskIds.set(key, item.id);
if (isCancelled(key, item.id)) {
item.reject(new Error("任务已取消"));
runningTaskIds.delete(key);
checkAndCleanQueue(key);
return;
}
try {
const result = await item.task();
if (isCancelled(key, item.id)) {
item.reject(new Error("任务已取消"));
}
else {
item.resolve(result);
}
}
catch (error) {
if (!isCancelled(key, item.id)) {
item.reject(error);
}
}
finally {
runningTaskIds.delete(key);
// 执行完后清除 pending 记录 (虽然在开始执行前其实已经不在 pending 里了,但为了保险)
if (((_a = pendingDebounceItem.get(key)) === null || _a === void 0 ? void 0 : _a.id) === item.id) {
pendingDebounceItem.delete(key);
}
checkAndCleanQueue(key);
}
};
/**
* 队列请求方法
* @param fn 数据请求函数
* @param options 配置项 (包含 key, duration)
*/
export default function queue(fn, options) {
const { key = "default-queue", duration } = options !== null && options !== void 0 ? options : {};
// 参数验证
if (!key || typeof key !== "string") {
return Promise.reject(new Error("队列 key 必须是有效的字符串"));
}
if (typeof fn !== "function") {
return Promise.reject(new Error("任务必须是一个返回 Promise 的函数"));
}
return new Promise((resolve, reject) => {
var _a;
const id = Symbol(`${key}-${Date.now()}-${Math.random()}`);
// 确定是否启用防抖模式及时间
const isDebounceMode = typeof duration === "number" || duration === true;
let debounceTime = 0;
if (typeof duration === "number") {
// 验证防抖时间必须是非负数
if (duration < 0) {
reject(new Error("防抖时间不能为负数"));
return;
}
debounceTime = duration;
}
else if (duration === true) {
debounceTime = DEFAULT_DEBOUNCE_TIME;
}
// 初始化取消集合
if (!cancelledIds.has(key)) {
cancelledIds.set(key, new Set());
}
if (isDebounceMode) {
// 防抖模式:
// 1. 标记之前的任务为取消 (包括正在 pending 的和正在执行的)
markPreviousAsCancelled(key, id);
const item = {
id,
task: fn,
resolve,
reject
};
// 2. 设置定时器
item.timer = setTimeout(() => {
var _a;
// 定时器结束,执行任务
// 从 pending 中移除 (因为它即将开始执行)
if (((_a = pendingDebounceItem.get(key)) === null || _a === void 0 ? void 0 : _a.id) === id) {
pendingDebounceItem.delete(key);
executeTask(key, item);
}
}, debounceTime);
// 3. 存入 pending
// 类型兼容修正: 使 resolve 和 reject 适配 unknown
pendingDebounceItem.set(key, {
...item,
resolve: (value) => resolve(value),
reject
});
}
else {
// 普通串行模式
const queueList = (_a = queues.get(key)) !== null && _a !== void 0 ? _a : [];
if (!queues.has(key)) {
queues.set(key, queueList);
}
queueList.push({
id,
task: fn,
// 类型兼容修正: 使 resolve 和 reject 适配 unknown
resolve: (value) => resolve(value),
reject
});
processQueue(key);
}
});
}
//# sourceMappingURL=index.js.map