版权声明:原创内容,转载请标明作者和出处。
export class CancellationError extends Error {
constructor() {
super("Promise was cancelled");
this.name = "CancellationError";
}
}
/**
* 创建一个可取消的 Promise。返回一个包含 Promise 和取消函数的对象。
*
* @param {Promise<T> | T} promise - 需要变为可取消的 Promise,或者是一个同步的值。
* @returns {{ promise: Promise<T>; cancel: () => void }} - 包含可取消的 Promise 和取消函数的对象。
*
* @author zilin
*
* @example
*
* // 创建一个可取消的 Promise
* const { promise, cancel } = cancellablePromise(
* new Promise(resolve => setTimeout(() => resolve('resolved'), 1000))
* );
*
* // 500 毫秒后取消 Promise
* setTimeout(() => {
* cancel();
* }, 500);
*
* // Promise 将被取消,所以这将抛出一个 CancellationError
* promise.then(console.log).catch(console.error);
*/
export function cancellablePromise<T>(promise: Promise<T> | T): {
promise: Promise<T>;
cancel: () => void;
} {
let isCancelled = false;
const wrappedPromise = new Promise<T>((resolve, reject) => {
Promise.resolve(promise).then(
(val) => (isCancelled ? reject(new CancellationError()) : resolve(val)),
(error) => {
// Check if the promise is cancelled
if (isCancelled) {
reject(new CancellationError());
} else {
// If not, throw the original error
reject(error);
}
}
);
});
return {
promise: wrappedPromise,
cancel() {
isCancelled = true;
},
};
}
import { cancellablePromise, CancellationError } from "./cancellablePromise";
export type AsyncFunc<T, U extends unknown[]> = (...args: U) => Promise<T>;
export interface TakeLatestPromiseOptions {
/** 一个 promise 被取消,是否抛出错误。默认为 `false` */
throwOnCancellation?: boolean;
}
type TakeLatestPromiseReturn<T, Options> = Options extends {
throwOnCancellation: true;
}
? T
: T | undefined;
export function takeLatestPromise<T, U extends unknown[]>(
asyncFunction: AsyncFunc<T, U>,
options: { throwOnCancellation: true }
): AsyncFunc<T, U>;
export function takeLatestPromise<T, U extends unknown[]>(
asyncFunction: AsyncFunc<T, U>,
options?: TakeLatestPromiseOptions | undefined
): AsyncFunc<T | undefined, U>;
/**
* 包装一个异步函数,返回一个新函数,该函数确保只有最新的调用会被完成。
* 如果在上一个调用完成之前进行了新的调用,那么上一个调用将被取消。
*
* @param {AsyncFunc<T, U>} asyncFunction - 需要包装的异步函数。
* @param {Options} [options] - 可选的配置对象。
* @param {boolean} [options.throwOnCancellation = false] - 如果一个 promise 被取消,是否抛出错误。默认为 `false`。
* @returns {AsyncFunc<T | undefined, U>} - 一个新函数,返回一个 promise,该 promise 解析为最新调用的结果,或者如果 promise 被取消,则为 `undefined`。
*
* @author zilin
*
* @example
*
* const fetchData = async (id) => {
* const response = await fetch(`/api/data/${id}`);
* const data = await response.json();
* return data;
* };
*
* const enhancedFetchData = takeLatestPromise(fetchData, { throwOnCancellation: true });
*
* // 只有最新调用的结果会被打印出来。
* enhancedFetchData(1).then(console.log);
* enhancedFetchData(2).then(console.log);
* enhancedFetchData(3).then(console.log);
*
* // 如果一个 promise 被取消,将会抛出一个错误。
* try {
* await enhancedFetchData(1);
* } catch (error) {
* console.error(error); // 输出: "Error: Promise was cancelled"
* }
*/
export function takeLatestPromise<T, U extends unknown[]>(
asyncFunction: AsyncFunc<T, U>,
options?: TakeLatestPromiseOptions | undefined
) {
let lastTask: ReturnType<typeof cancellablePromise> | null = null;
return async function (
...args: U
): Promise<TakeLatestPromiseReturn<T, typeof options>> {
if (lastTask) {
lastTask.cancel();
}
lastTask = cancellablePromise(asyncFunction(...args));
try {
const result = await lastTask.promise;
return result as T;
} catch (error) {
if (error instanceof CancellationError) {
if (options?.throwOnCancellation) {
throw error;
} else {
return undefined;
}
} else {
throw error;
}
}
};
}
export class RetryError extends Error {
constructor() {
super("Maximum retry attempts exceeded");
this.name = "RetryError";
}
}
/**
* 创建一个可重试的 Promise。如果 Promise 被拒绝,将尝试重新运行,直到 Promise 被解析或达到最大重试次数。
*
* @param {() => Promise<T>} promiseFunc - 需要重试的异步操作。
* @param {number} maxAttempts - 最大重试次数。
* @returns {Promise<T>} - 一个新的 Promise,如果操作成功或达到最大重试次数,则解析为原始值,否则抛出一个错误。
*
* @author zilin
*
* @example
*
* // 创建一个可重试的 Promise
* const promise = retryPromise(
* () => new Promise((resolve, reject) => Math.random() > 0.5 ? resolve('Success') : reject('Failure')),
* 5
* );
*
* // 这个 Promise 会尝试运行异步操作,直到操作成功或达到 5 次重试
* promise.then(console.log).catch(console.error);
*/
export function retryPromise<T>(
promiseFunc: () => Promise<T>,
maxAttempts: number
): Promise<T> {
return new Promise<T>(async (resolve, reject) => {
for (let i = 0; i < maxAttempts; i++) {
try {
const result = await promiseFunc();
return resolve(result);
} catch (error) {
if (i === maxAttempts - 1) {
return reject(new RetryError());
}
}
}
});
}
export class TimeoutError extends Error {
constructor() {
super("Promise timed out");
this.name = "TimeoutError";
}
}
/**
* 创建一个可超时的 Promise。如果在指定的时间内 Promise 未能完成,将抛出一个错误。
*
* @param {Promise<T> | T} promise - 需要变为可超时的 Promise,或者是一个同步的值。
* @param {number} timeout - 超时时间,单位为毫秒。
* @returns {Promise<T>} - 一个新的 Promise,如果在指定的时间内完成,则解析为原始值,否则抛出一个错误。
*
* @example
*
* // 创建一个可超时的 Promise
* const promise = timeoutPromise(
* new Promise(resolve => setTimeout(() => resolve('resolved'), 2000)),
* 1000
* );
*
* // 由于原始 Promise 将在 2 秒后完成,而超时时间设置为 1 秒,所以这将抛出一个 TimeoutError
* promise.then(console.log).catch(console.error);
*/
export function timeoutPromise<T>(
promise: Promise<T> | T,
timeout: number
): Promise<T> {
/** 兼容 Node.js 和浏览器环境 */
let timer: ReturnType<typeof setTimeout>;
const timeoutPromise = new Promise<T>((_, reject) => {
timer = setTimeout(() => {
reject(new TimeoutError());
}, timeout);
});
return Promise.race([promise, timeoutPromise]).finally(() => {
clearTimeout(timer);
});
}
/**
* 创建一个可复用的 Promise 实例。如果第二次调用是前一个Promise还在执行,则返回前一个 Promise 实例
*/
/**
* 自带缓存能力的 Promise 封装,如果参数保持一致,则复用之前的请求体,不会重新触发请求
*
* @param api 真正的请求函数
* @param hash hash 函数
*/
export function reusePromise<Params, Result>(
api: (params: Params) => Promise<Result>,
generateHash: (params: Params) => string
): (params: Params) => Promise<Result> {
/**
* 基于 hash 来做请求状态的复用
*/
const duringAPIPromiseHashMappings: Record<
string,
Promise<Result> | undefined
> = {};
return (params) => {
const currentHash = generateHash(params);
const existPromise = duringAPIPromiseHashMappings[currentHash];
// 如果存在请求中的数据结构
if (existPromise) {
return existPromise; // 等待 Promise 结束
}
// 等待这次流程处理结束,将 hash 的缓存删除
const res = (duringAPIPromiseHashMappings[currentHash] = api(
params
).finally(() => {
delete duringAPIPromiseHashMappings[currentHash];
}));
return res;
};
}