异步防抖函数详解
下面我将详细解释这段异步防抖函数的实现原理和工作机制:
static asyncDebounce<T extends (...args: any[]) => Promise<R>, R>(
fn: T,
delay: number
): (...args: Parameters<T>) => Promise<R> {
// 状态变量
let timer: number | null = null;
let pendingPromise: Promise<R> | null = null;
let pendingResolve: ((value: R) => void) | null = null;
let pendingReject: ((reason?: any) => void) | null = null;
let lastArgs: Parameters<T> | null = null;
return async (...args: Parameters<T>): Promise<R> => {
// 保存最新参数
lastArgs = args;
// 清除之前的定时器
if (timer) {
clearTimeout(timer);
}
// 如果已有等待中的 Promise,拒绝它(表示被新调用取代)
if (pendingPromise) {
if (pendingReject) {
pendingReject(new Error("Debounced call superseded"));
}
pendingPromise = null;
}
// 创建新的 Promise
pendingPromise = new Promise<R>((resolve, reject) => {
pendingResolve = resolve;
pendingReject = reject;
});
// 设置新的定时器
timer = setTimeout(async () => {
try {
// 执行实际函数
const result = await fn(...(lastArgs as Parameters<T>));
// 解析 Promise
if (pendingResolve) {
pendingResolve(result);
}
} catch (error) {
// 拒绝 Promise
if (pendingReject) {
pendingReject(error);
}
} finally {
// 清理状态
timer = null;
pendingPromise = null;
pendingResolve = null;
pendingReject = null;
lastArgs = null;
}
}, delay);
return pendingPromise;
};
}
工作原理详解
1. 函数签名和泛型参数
static asyncDebounce<T extends (...args: any[]) => Promise<R>, R>(
fn: T,
delay: number
): (...args: Parameters<T>) => Promise<R>
T:表示一个函数类型,该函数接受任意参数并返回一个 PromiseR:表示 Promise 解析后的值的类型fn:需要防抖处理的异步函数delay:防抖延迟时间(毫秒)- 返回值:一个新的函数,具有相同的参数类型,返回一个 Promise
2. 状态变量
let timer: number | null = null;
let pendingPromise: Promise<R> | null = null;
let pendingResolve: ((value: R) => void) | null = null;
let pendingReject: ((reason?: any) => void) | null = null;
let lastArgs: Parameters<T> | null = null;
timer:存储 setTimeout 的 ID,用于取消定时器pendingPromise:当前挂起的 PromisependingResolve:用于解决当前 Promise 的函数pendingReject:用于拒绝当前 Promise 的函数lastArgs:保存最后一次调用的参数
3. 返回的防抖函数
当调用防抖函数时:
return async (...args: Parameters<T>): Promise<R> => {
// 1. 保存最新参数
lastArgs = args;
// 2. 清除之前的定时器
if (timer) {
clearTimeout(timer);
}
// 3. 取消前一个未决的Promise
if (pendingPromise) {
if (pendingReject) {
pendingReject(new Error("Debounced call superseded"));
}
pendingPromise = null;
}
// 4. 创建新的Promise
pendingPromise = new Promise<R>((resolve, reject) => {
pendingResolve = resolve;
pendingReject = reject;
});
// 5. 设置新的定时器
timer = setTimeout(async () => {
// 定时器到期后的操作...
}, delay);
// 6. 返回新创建的Promise
return pendingPromise;
};
4. 定时器回调(防抖核心)
当定时器到期后执行:
timer = setTimeout(async () => {
try {
// 执行实际函数
const result = await fn(...(lastArgs as Parameters<T>));
// 解析Promise
if (pendingResolve) {
pendingResolve(result);
}
} catch (error) {
// 拒绝Promise
if (pendingReject) {
pendingReject(error);
}
} finally {
// 清理状态
timer = null;
pendingPromise = null;
pendingResolve = null;
pendingReject = null;
lastArgs = null;
}
}, delay);
工作流程可视化
graph TD
A[调用防抖函数] --> B[保存参数 lastArgs]
B --> C[清除旧定时器]
C --> D[取消前一个Promise]
D --> E[创建新Promise]
E --> F[设置新定时器]
F --> G[返回新Promise]
G --> H{等待 delay 毫秒}
H -- 期间有新调用 --> B
H -- 无新调用 --> I[执行原始函数 fn]
I --> J[成功]
J --> K[解析Promise]
I --> L[失败]
L --> M[拒绝Promise]
K & M --> N[清理所有状态]
防抖过程详解
-
首次调用
- 保存参数
- 创建新的 Promise
- 设置定时器
- 返回 Promise
-
在 delay 时间内再次调用
- 清除之前的定时器
- 拒绝之前的 Promise(通知它被取代)
- 保存新参数
- 创建新的 Promise
- 设置新的定时器
- 返回新的 Promise
-
delay 时间内无新调用
- 定时器到期
- 执行原始函数 fn
- 根据执行结果解决或拒绝 Promise
- 清理所有状态变量
关键特性
-
参数更新
- 总是使用最后一次调用的参数
- 中间调用的参数会被丢弃
-
Promise 管理
- 每次调用返回新的 Promise
- 当被新调用取代时,前一个 Promise 会被拒绝
- 只有最后一次调用的 Promise 会被解决
-
错误处理
- 原始函数抛出的错误会传递到 Promise
- 被取代的 Promise 收到特定错误
-
资源清理
- 执行完成后重置所有状态
- 避免内存泄漏
使用示例
// 模拟一个异步API调用
async function fetchData(query: string): Promise<string[]> {
console.log(`Fetching data for: ${query}`);
// 模拟网络请求
await new Promise(resolve => setTimeout(resolve, 200));
return [`result1 for ${query}`, `result2 for ${query}`];
}
// 创建防抖版本
const debouncedFetch = Utility.asyncDebounce(fetchData, 500);
// 模拟用户连续输入
async function simulateUserInput() {
try {
const results = await debouncedFetch("a");
console.log("Results for 'a':", results);
} catch (err) {
if (err.message === "Debounced call superseded") {
console.log("Request for 'a' was superseded");
}
}
try {
const results = await debouncedFetch("ab");
console.log("Results for 'ab':", results);
} catch (err) {
if (err.message === "Debounced call superseded") {
console.log("Request for 'ab' was superseded");
}
}
try {
const results = await debouncedFetch("abc");
console.log("Results for 'abc':", results);
} catch (err) {
console.error("Error for 'abc':", err);
}
}
simulateUserInput();
// 输出:
// (500ms后)
// Fetching data for: abc
// Request for 'a' was superseded
// Request for 'ab' was superseded
// Results for 'abc': ["result1 for abc", "result2 for abc"]
应用场景
-
搜索建议
- 用户输入时实时获取搜索建议
- 避免每次按键都发送请求
-
表单自动保存
- 用户编辑表单时自动保存
- 避免频繁保存操作
-
API调用优化
- 防止重复提交
- 减少不必要的API调用
-
实时数据过滤
- 用户调整过滤器时更新数据
- 避免频繁重渲染