id-scanner-lib
Version:
Browser-based ID card, QR code, and face recognition scanner with liveness detection
283 lines (252 loc) • 5.86 kB
text/typescript
/**
* @file 重试工具
* @description 提供重试逻辑功能
* @module utils/retry
*/
/**
* 重试选项
*/
export interface RetryOptions {
/** 最大重试次数 */
maxAttempts?: number;
/** 初始等待时间(ms) */
initialDelay?: number;
/** 最大等待时间(ms) */
maxDelay?: number;
/** 指数退避因子 */
backoffFactor?: number;
/** 是否随机抖动 */
jitter?: boolean;
/** 重试条件 */
shouldRetry?: (error: unknown) => boolean;
}
/**
* 默认重试选项
*/
const DEFAULT_OPTIONS: Required<RetryOptions> = {
maxAttempts: 3,
initialDelay: 1000,
maxDelay: 10000,
backoffFactor: 2,
jitter: true,
shouldRetry: () => true
};
/**
* 带重试的异步函数包装器
* @param fn 要执行的异步函数
* @param options 重试选项
* @returns 函数结果
*/
export async function withRetry<T>(
fn: () => Promise<T>,
options: RetryOptions = {}
): Promise<T> {
const opts = { ...DEFAULT_OPTIONS, ...options };
let lastError: unknown;
let delay = opts.initialDelay;
for (let attempt = 1; attempt <= opts.maxAttempts; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error;
// 检查是否应该重试
if (!opts.shouldRetry(error)) {
throw error;
}
// 如果不是最后一次尝试,等待后重试
if (attempt < opts.maxAttempts) {
// 添加随机抖动
const jitterAmount = opts.jitter ? Math.random() * delay : 0;
const waitTime = Math.min(delay + jitterAmount, opts.maxDelay);
await sleep(waitTime);
// 指数退避
delay = Math.min(delay * opts.backoffFactor, opts.maxDelay);
}
}
}
throw lastError;
}
/**
* 睡眠函数
* @param ms 毫秒数
*/
function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* 创建指数退避重试函数
* @param options 重试选项
* @returns 包装后的重试函数
*/
export function createRetryable<T extends (...args: any[]) => Promise<any>>(
fn: T,
options: RetryOptions = {}
): T {
const wrapped = (...args: Parameters<T>): Promise<ReturnType<T>> => {
return withRetry(() => fn(...args), options);
};
Object.defineProperty(wrapped, 'name', { value: fn.name, configurable: true });
return wrapped as T;
}
/**
* 异步缓存
* @description 用于缓存异步函数的结果
*/
export class AsyncCache<T> {
private cache: Map<string, { value: T; expiry: number }> = new Map();
private defaultTTL: number;
/**
* @param defaultTTL 默认过期时间(毫秒)
*/
constructor(defaultTTL: number = 5 * 60 * 1000) {
this.defaultTTL = defaultTTL;
}
/**
* 获取缓存值
* @param key 缓存键
* @returns 缓存值,如果不存在或已过期则返回undefined
*/
get(key: string): T | undefined {
const entry = this.cache.get(key);
if (!entry) return undefined;
if (Date.now() > entry.expiry) {
this.cache.delete(key);
return undefined;
}
return entry.value;
}
/**
* 设置缓存值
* @param key 缓存键
* @param value 缓存值
* @param ttl 过期时间(毫秒)
*/
set(key: string, value: T, ttl?: number): void {
this.cache.set(key, {
value,
expiry: Date.now() + (ttl || this.defaultTTL)
});
}
/**
* 删除缓存值
* @param key 缓存键
*/
delete(key: string): void {
this.cache.delete(key);
}
/**
* 清空缓存
*/
clear(): void {
this.cache.clear();
}
/**
* 获取缓存大小
*/
get size(): number {
// 清理过期项 (避免在迭代中删除)
const now = Date.now();
const expiredKeys: string[] = [];
for (const [key, entry] of this.cache.entries()) {
if (now > entry.expiry) {
expiredKeys.push(key);
}
}
expiredKeys.forEach(key => this.cache.delete(key));
return this.cache.size;
}
/**
* 异步获取或设置缓存
* @param key 缓存键
* @param fn 获取值的函数
* @param ttl 过期时间
* @returns 缓存值
*/
async getOrSet(
key: string,
fn: () => Promise<T>,
ttl?: number
): Promise<T> {
const cached = this.get(key);
if (cached !== undefined) {
return cached;
}
const value = await fn();
this.set(key, value, ttl);
return value;
}
}
/**
* 简单信号量
* @description 用于控制并发数量
*/
export class Semaphore {
private permits: number;
private queue: Array<() => void> = [];
/**
* @param permits 最大许可数
*/
constructor(permits: number) {
this.permits = permits;
}
/**
* 获取许可
* @returns Promise
*/
async acquire(): Promise<void> {
if (this.permits > 0) {
this.permits--;
return Promise.resolve();
}
return new Promise<void>(resolve => {
this.queue.push(resolve);
});
}
/**
* 释放许可
*/
release(): void {
this.permits++;
const next = this.queue.shift();
if (next) {
this.permits--;
next();
}
}
/**
* 尝试获取许可
* @returns 是否获取成功
*/
tryAcquire(): boolean {
if (this.permits > 0) {
this.permits--;
return true;
}
return false;
}
/**
* 获取当前可用许可数
*/
get availablePermits(): number {
return this.permits;
}
}
/**
* 带信号量的异步函数包装器
* @param fn 异步函数
* @param semaphore 信号量
* @returns 包装后的函数
*/
export function withSemaphore<T extends (...args: any[]) => Promise<any>>(
fn: T,
semaphore: Semaphore
): T {
return (async (...args: Parameters<T>): Promise<ReturnType<T>> => {
await semaphore.acquire();
try {
return await fn(...args);
} finally {
semaphore.release();
}
}) as T;
}