很多人都玩不转的异步函数,我写给你看呀

561 阅读3分钟
  1. 优雅的给 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();
    },
  });
}
  1. 优雅的给 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 };
}

  1. 优雅处理几十万 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();
    }
  }
}

  1. 优雅的手动强制退出 async 函数,参考 async 函数超时退出的逻辑呀, easy ~