typing.d.ts
/**
* 自定义构建唯一标识的方法
*/
export type KeyBuilderFun<P extends unknown[]> = (...args: P) => string
export type Service<R, P extends unknown[]> = (...args: P) => Promise<R>
ShareablePromise.ts
import { KeyBuilderFun, Service } from './typing'
/**
* 构建可共享Pending状态Promise结果的异步方法
* @param fn await修饰的异步方法,或返回Promise结果的异步方法
* @param keyBuilder 自定义构建可共享pending状态promise的唯一标识方法,或直接指定唯一标识
* @returns 被包装之后的fn方法,此包装方法支持自动共享pending状态的promise
*/
export function buildShareablePendingPromiseMethod<R, P extends unknown[]>(
fn: Service<R, P>,
keyBuilder: KeyBuilderFun<P> | string
): Service<R, P> {
let lastPromise: R | undefined
let lastKey: string | undefined
function doRun(key: string, ...args: P) {
lastKey = key
// @ts-ignore
const retPromise = fn.apply<unknown, P, R>(this, args).finally(() => {
if (lastKey === key) {
// 如果当前key和lastKey是同一个,则清空
lastKey = undefined
lastPromise = undefined
}
})
//@ts-ignore
lastPromise = retPromise
return retPromise
}
return function (...args: P) {
const tmpLastPromise = lastPromise
const key =
typeof keyBuilder === 'string' ? keyBuilder : keyBuilder(...args)
if (lastKey === key && tmpLastPromise) {
console.log('使用 pending 缓存', key, lastKey, lastPromise)
// 当前key与上一个key一致,且存在pending中的promise,则直接返回该pending中的promise
return tmpLastPromise
} else {
// 没有与当前key相同的pending中的promise,则直接执行
return doRun(key, ...args)
}
}
}
CancelablePromise.ts
import { KeyBuilderFun, Service } from './typing'
export class CancelPendingPromiseError extends Error {
constructor(msg?: string) {
super(msg)
}
}
/**
* 构建可取消promise
* @param p 原始的promise
* @returns
*/
export function buildCancelablePromise<T>(p: Promise<T>) {
// 超时取消函数
let outReject: undefined | ((reason?: unknown) => void)
const rejectPromise = new Promise<void>((_, reject) => {
outReject = reject
})
/**
* 取消promise(实际是触发promise的reject回调)
* @param reason 取消原因。与promise的reject回调方法的入参类型一致
*/
function cancel(reason: string = '取消pending Promise') {
if (outReject) outReject(new CancelPendingPromiseError(reason))
else throw 'promise的reject方法未初始化'
}
const cancelablePromise = Promise.race([p, rejectPromise])
return {
/**
* 可取消的promise
*/
cancelablePromise,
/**
* 取消promise(实际是触发promise的reject回调)
* @param reason 取消原因。与promise的reject回调方法的入参类型一致
*/
cancel,
}
}
/**
* 构建可自动取消的异步方法
* @param fn await修饰的异步方法,或返回Promise结果的异步方法
* @param keyBuilder 自定义构建可取消promise的唯一标识方法,或直接指定唯一标识
* @returns 被包装之后的fn方法,此包装方法支持自动取消
*/
export function buildCancelablePromiseMethod<R, P extends unknown[]>(
fn: Service<R, P>,
keyBuilder: KeyBuilderFun<P> | string
): Service<R, P> {
let executing = false
let lastKey: string | undefined
let lastCancel: undefined | ((reason?: string) => void)
function doRun(key: string, ...args: P) {
executing = true
lastKey = key
const retPromise = fn
// @ts-ignore
.apply<unknown, P, Promise<R>>(this, args)
.finally(() => {
executing = false
lastCancel = undefined
})
const { cancelablePromise, cancel } = buildCancelablePromise(retPromise)
lastCancel = cancel
return cancelablePromise as Promise<R>
}
return function (...args: P) {
const key =
typeof keyBuilder === 'string' ? keyBuilder : keyBuilder(...args)
console.log('key对应promise状态:', key, executing, lastKey)
if (!executing) {
// 发起新请求
return doRun(key, ...args)
} else {
const tmpLastCancel = lastCancel
if (lastKey !== key && tmpLastCancel) {
// 取消执行中
console.log(`取消promise:${lastKey}`)
// 这里不会中断程序运行,后面的doRun还是能够执行,只会会使得上一个调用的地方,出现CancelPendingPromiseError异常。如果UI组件未捕获该异常的话,会导致界面出错
tmpLastCancel(`取消promise:${lastKey}`)
}
// 发起新请求
return doRun(key, ...args)
}
}
}
CacheHelper.ts
interface CacheItem {
value: any; // 缓存的值,可以根据实际类型替换为具体类型
expireTime: number; // 过期时间戳
}
export class CacheHelper {
private store: { [key: string]: CacheItem } = {};
/**
* 获取缓存
* @param key 缓存键
* @returns 缓存值,如果不存在或已过期则返回 null
*/
getCache(key: string): any | null {
const cache = this.store[key];
if (!cache) {
return null;
}
const curTime = new Date().getTime();
if (curTime > cache.expireTime) {
// 删除已缓存的数据
this.removeCache(key);
return null;
}
return cache.value;
}
/**
* 删除缓存
* @param key 缓存键
*/
removeCache(key: string): void {
delete this.store[key];
}
/**
* 设置缓存
* @param key 缓存键
* @param value 缓存值
* @param expireTime 过期时间(毫秒),默认 5 分钟
*/
setCache(key: string, value: any, expireTime: number = 1000 * 60 * 5): void {
this.store[key] = {
value,
expireTime: new Date().getTime() + expireTime,
};
}
/**
* 全局缓存包装器(支持异步函数)
* @param fn 需要缓存的函数
* @param keyBuilder 缓存键生成器(可以是字符串或函数)
* @param time 缓存过期时间(毫秒),默认 5 分钟
* @returns 包装后的函数
*/
promiseGlobalCacheWrapper(
fn: (...args: any[]) => Promise<any>,
keyBuilder: string | ((...args: any[]) => string),
time: number = 1000 * 60 * 5
): (...args: any[]) => Promise<any> {
return async (...args: any[]): Promise<any> => {
const key = typeof keyBuilder === 'string' ? keyBuilder : keyBuilder(...args);
const cache = this.getCache(key);
if (cache) {
return cache;
}
const value = await fn(...args);
if (value) {
this.setCache(key, value, time);
}
return value;
};
}
}