- 优雅的给 async 函数加重试,如给某个 async 函数加上重试三次的逻辑。easy~
import awaitToJs from 'await-to-js'
export function ProxyAsyncFn<T, U>(promiseFn: (...args: U[]) => Promise<T>): (...args: U[]) => Promise<T> {
return new Proxy(promiseFn, {
apply(fn, thisArg, args) {
async function innerRetry(retry = 3) {
const [err, ret] = await awaitToJs(fn(...args));
if (err) {
if (retry === 0) throw err;
return await innerRetry(--retry);
}
return ret;
}
return innerRetry();
},
});
}
- 优雅的给 async 函数加上超时退出功能,如两分钟没执行完的 async 函数强制 reject 。easy~
* @description: 给异步方法加上超时报错策略,不用Promise.race 是因为其不会删除定时器,Promise.race(PromiseA,2分钟定时器),如果promiseA 在 10 s 执行完毕,2分钟后会执行定时器代码。
* @param {T} p
* @param {number} timeout
* @return {Promise<void>}
*/
export function race<T = any>(p: Promise<T>, config: { timeout: number; isResolve: true }): Promise<T | undefined>;
export function race<T = any>(p: Promise<T>, config: { timeout: number; isResolve?: false }): Promise<T>;
export async function race<T = any>(
p: Promise<T>,
{ timeout, isResolve = false }: { timeout: number; isResolve?: boolean }
): Promise<T | undefined> {
return new Promise((resolve, reject) => {
let timer: NodeJS.Timeout | null = null;
Promise.resolve(
p.then(
data => {
timer && clearTimeout(timer);
timer = null;
return resolve(data);
},
err => {
timer && clearTimeout(timer);
timer = null;
return reject(err);
}
)
);
Promise.resolve(
new Promise(() => {
timer = setTimeout(() => {
return isResolve ? resolve(void 0) : reject(new Error('Race callback method execution timeout'));
}, timeout);
})
);
});
}
import awaitToJs from 'await-to-js';
import { IncludeUndefinedAble, UnPromisify } from '../interface/base';
import { race } from './race';
// 给 异步函数 增加一个超时退出功能
export async function awaitControl<T extends Promise<any>>(
p: T,
timeout = 20 * 1000
): Promise<{ overtime: boolean; res: IncludeUndefinedAble<UnPromisify<T>>; error: Error | null }> {
const ret = (await race(awaitToJs(p), { timeout, isResolve: true })) as IncludeUndefinedAble<
[Error | null, UnPromisify<T>]
>;
if (Array.isArray(ret)) return { res: ret[1], error: ret[0], overtime: false };
return { res: void 0, error: new Error('awaitControl callback function timeout'), overtime: true };
}
- 优雅处理几十万 url 并发问题 easy~
/* eslint-disable no-param-reassign */
/**
* @description: BatchProcess
*/
import { awaitControl } from './awaitControl';
// 使用排队模型 + 事件中心处理异步 以达到最少时间处理完所有异步
export default class BatchProcess<T = string, U = unknown, M = void> {
static timeout = 5 * 60 * 1000;
errorPool: T[] = [];
readonly successPool: T[] = [];
private pool: T[] = [];
private max = 10;
private processResult: M[] = [];
private result: U[] = [];
private event: ((param: T) => Promise<U>) | undefined;
private success: ((response: U, param: T) => M) | undefined;
private done: ((subscribeOne: M[], result: U[]) => void) | undefined;
private isTimeOver = false;
// eslint-disable-next-line @typescript-eslint/no-parameter-properties
constructor(private logId: string, max = 10) {
this.max = max;
}
// 对事件集中封装
on(event: 'subscribe', callback: (param: T) => Promise<U>): void;
on(event: 'success', callback: (response: U, param: T) => M): void;
on(event: 'done', callback: (processResult: M[], result: U[]) => void): void;
on(
event: 'success' | 'done' | 'subscribe',
callback: { (param: T): Promise<U>; (response: U, param: T): M; (processResult: M[], result: U[]): void }
): void {
if (event === 'subscribe') this.subscribe(callback);
if (event === 'success') this.subscribeSuccess(callback);
if (event === 'done') this.subscribeDone(callback);
}
async emit(pool: T[], { retry, timeout }: { retry: number; timeout?: number } = { retry: 0 }): Promise<void> {
if (!this.event) throw new Error('Please subscribe to the subscribe event first');
timeout = timeout ?? BatchProcess.timeout;
const timer = setTimeout(() => {
this.isTimeOver = true;
}, timeout);
this.pool = pool;
this.errorPool = [];
this.max = Math.min(this.max, this.pool.length);
retry = Math.min(retry, 0);
// 强制结束 emit,subscribeOne 可能需要一定的执行时间不能立马结束。
await awaitControl(Promise.all(new Array(this.max).fill(1).map(() => this.subscribeOne())), timeout);
// 对错误的回调重试
while (retry && !this.isTimeOver) {
await this.emit(this.errorPool, { retry: retry - 1, timeout });
}
this.done?.(this.processResult, this.result);
clearTimeout(timer);
}
checkTimeOver(): boolean {
return this.isTimeOver;
}
private subscribe(callback: (param: T) => Promise<U>): void {
if (this.event) throw new Error('Subscribed to subscribe event ');
this.event = callback;
}
private subscribeSuccess(callback: (response: U, param: T) => M): void {
if (this.success) throw new Error('Subscribed to success event ');
this.success = callback;
}
private subscribeDone(callback: (processResult: M[], result: U[]) => void): void {
if (this.done) throw new Error('Subscribed to done event ');
this.done = callback;
}
// 处理单个回调
private async subscribeOne(): Promise<void> {
if (this.isTimeOver) return;
while (this.pool.length) {
let current = this.pool.shift()!;
let { error, res, overtime } = await awaitControl(this.event!(current), 60 * 1000);
if (overtime) {
console.error(this.logId, `[Download Error,the url is ${current}]`);
}
if (error) {
this.errorPool.push(current);
} else {
this.successPool.push(current);
this.result.push(res!);
this.processResult.push(this.success?.(res!, current)!);
}
await this.subscribeOne();
}
}
}
- 优雅的手动强制退出 async 函数,参考 async 函数超时退出的逻辑呀, easy ~