处理 Table 列表内多处单独请求状态的问题

44 阅读4分钟

1、封装带 cancel 的 Promise

export enum IPromiseStatus {
  PENDING = 'pending',
  FULFILLED = 'fulfilled',
  REJECTED = 'rejected',
  CANCELED = 'canceled'
}

// 可取消的 Promise
export class CancelablePromise<T> {
  private status: IPromiseStatus = IPromiseStatus.PENDING;
  private doResolve!: (value: T | PromiseLike<T>) => void;
  private doReject!: (reason?: unknown) => void;
  readonly promise: Promise<T>;

  constructor() {
    // 通过 new CancelablePromise().Promise 拿到处理后的值
    this.promise = new Promise<T>((resolve, reject) => {
      this.doResolve = resolve;
      this.doReject = reject;
    });
  }

  // 接受 resolve 的值
  resolve = (value: T | PromiseLike<T>) => {
    if (this.status === IPromiseStatus.PENDING) {
      this.status = IPromiseStatus.FULFILLED;
      this.doResolve(value);
    }
  };

  // 接受 reject 的值
  reject = (reason?: unknown) => {
    if (this.status === IPromiseStatus.PENDING) {
      this.status = IPromiseStatus.REJECTED;
      this.doReject(reason);
    }
  };

  // 取消 Promise
  cancel = (reason?: unknown) => {
    if (this.status === IPromiseStatus.PENDING) {
      this.status = IPromiseStatus.CANCELED;
      this.doReject(reason === undefined ? this : reason);
    }
  };

  // 获取 Promise 的状态
  getStatus = (): IPromiseStatus => {
    return this.status;
  };
  
  // 获取 Promise 处理后的结果
  getResulet = (): T | unknow => {
    return this.promise;
  };
}

2、封装请求任务 Task

// 任务执行器
export type ITaskRunner<P, R> = (payload: P) => Promise<R>;

// 单个任务状态
export enum ITaskStatus {
  PENDING = 'pending', // 待执行
  RUNNING = 'running', // 执行中
  SUCCESSFUL = 'successful', // 执行成功
  FAILED = 'failed' // 执行失败
}

// 批量任务状态
export enum IBatchTasksStatus {
  PENDING = 'pending',
  RUNNING = 'running',
  PAUSED = 'paused',
  CANCELED = 'canceled',
  COMPLETED = 'completed'
}

// 单个任务参数
export interface ITaskParams<P, R> {
  readonly id?: symbol;
  readonly payload: P;
  readonly run: ITaskRunner<P, R>;
}

// 单个任务方法
export interface ITask<P = unknown, R = unknown> {
  run: (onStatusChange?: TTaskStatusChangeListener) => Promise<R>;
  getId: () => symbol;
  getStatus: () => ITaskStatus;
  getResult: () => R | undefined;
  getError: () => Error | undefined;
  getPayload: () => P;
  isCompleted: () => boolean;
}

// 任务类
export class Task<P = unknown, R = unknown> implements ITask<P, R> {
  private id: symbol;
  private payload: P;
  private runner: ITaskRunner<P, R>; // 执行器
  private status: ITaskStatus = ITaskStatus.PENDING; // 当前任务状态
  private result?: R; // 执行成功结果
  private error?: Error; // 执行失败结果 
  private cancelablePromise?: CancelablePromise<R>; // 通过 CancelablePromise 处理返回结果

  constructor(conf: ITaskRunner<P, R>) {
    this.id = conf.id ?? Symbol(); // 为每个任务设置唯一 id
    this.payload = conf.payload; // 外部传入的参数
    this.runner = conf.run; // 外部传入要执行的方法
  }

  run = async (onStatusChange?: TTaskStatusChangeListener): Promise<R> => {
    if (!this.cancelablePromise) {
      this.cancelablePromise = new CancelablePromise();
    }

    try {
      if (this.status === ITaskStatus.RUNNING) {
        onStatusChange?.(this.status);
      } else {
        this.status = ETaskStatus.RUNNING;
        onStatusChange?.(this.status);
        const res = await this.runner(this.payload);

        this.status = ETaskStatus.SUCCESSFUL;
        this.result = res;
        this.error = undefined;
      }
    } catch (err) {
      this.status = ETaskStatus.FAILED;
      this.error = err instanceof Error ? err : undefined;
      this.result = undefined;
    }

    try {
      switch (this.status) {
        case ETaskStatus.SUCCESSFUL:
          this.cancelablePromise.resolve(this.result!);

          break;
        case ETaskStatus.FAILED:
          this.cancelablePromise.reject(this.error);

          break;
        default:
      }

      return await this.cancelablePromise.promise;
    } catch (err) {
      this.cancelablePromise.reject(err);

      throw err;
    } finally {
      onStatusChange?.(this.status);
      this.cancelablePromise = undefined;
    }
  };

  // 返回当前任务的 id
  getId = (): symbol => {
    return this.id;
  };

  // 返回当前任务的执行状态
  getStatus = (): ETaskStatus => {
    return this.status;
  };

  // 返回当前任务执行的结果
  getResult = (): R | undefined => {
    return this.result;
  };

  // 返回当前任务执行的错误信息
  getError = (): Error | undefined => {
    return this.error;
  };

  // 返回当前任务的参数
  getPayload = (): P => {
    return this.payload;
  };

  // 返回当前任务是否执行完成
  isCompleted = (): boolean => {
    return this.status === ETaskStatus.SUCCESSFUL || this.status === ETaskStatus.FAILED;
  };
}

3、封装任务调度器

export interface ITasksScheduler<T extends ITask = ITask> {
  onProgressChange?: TProgressListener;
  getBatchStatus: () => IBatchTasksStatus;
  getTasks: () => T[];
  statistic: () => IStatisticTasksStatusResult;
  resume: () => boolean;
  pause: () => boolean;
  cancel: () => boolean;
  runById: (id: symbol, ignoreIfSuccessful?: boolean) => Promise<T>;
  runTask: <TT extends ITask>(task: TT, ignoreIfSuccessful?: boolean) => Promise<TT>;
  run: () => Promise<T[]>;
}



  // 获取 Promise 处理后的结果
export class TasksScheduler<T extends ITask = ITask> implements ITasksScheduler<T> {
  private readonly conf: ITasksSchedulerConf<T>;
  private batchStatus: IBatchTasksStatus;
  private runningCount = 0;
  private cancelablePromise?: CancelablePromise<T[]>;
  private throttleInformProgress: DebouncedFuncLeading<() => void>;
  onProgressChange?: TProgressListener;

  constructor(conf: ITasksSchedulerConf<T>) {
    const {
      throttleConf = {
        wait: 500
      },
      tasks
    } = conf;

    this.conf = conf;
    this.batchStatus = IBatchTasksStatus.PENDING;
    
    // 通过包装 informProgress 获取 batchStatus、tasks 队列的状态
    this.throttleInformProgress = _throttle(
        this.informProgress,
        throttleConf.wait,
        throttleConf.options
    );
    
    // 如果是中途接管,可能有存在正在运行的任务,此时直接调用runTask,以初始化管理状态
    tasks.forEach(item => {
      // 如果当前任务是 running,执行 runTask
      if (item.getStatus() === ETaskStatus.RUNNING) {
        this.runTask(item).finally(this.next);
      }
    });
  }

  //  快速初始化任务调度器
  static quickInit = <P = unknown, R = unknown>(params: IQuickInitTasksSchedulerParams<P, R>): ITasksScheduler<ITask<P, R>> => {
    const { payloads, run, ...conf } = params;

    return new TasksScheduler({
      ...conf,
      tasks: payloads.map(payload => {
        return new Task({
          payload,
          run
        });
      })
    });
  };

  // 查询批量任务的状态
  private checkBatchStatusForWaitPromise = () => {
    if (this.cancelablePromise) {
      switch (this.batchStatus) {
        case IBatchTasksStatus.COMPLETED:
          this.cancelablePromise.resolve(this.conf.tasks);

          break;
        case IBatchTasksStatus.CANCELED:
          this.cancelablePromise.reject(new BatchTasksCanceledError());

          break;
        default:
      }
    }
  };

  // 获取 batchStatus、tasks 队列的状态
  private informProgress = () => {
    this.onProgressChange?.(this.batchStatus, this.conf.tasks);
  };

  // 返回最新状态、promise
  private handleProgress = () => {
    try {
      if (this.batchStatus === IBatchTasksStatus.RUNNING) {
        this.throttleInformProgress();
      } else {
        this.throttleInformProgress.cancel();
        this.informProgress();
      }
    } finally {
      this.checkBatchStatusForWaitPromise();
    }
  };

  // 设置批量任务状态
  private setBatchStatus = (batchStatus: IBatchTasksStatus): boolean => {
    if (
      // 批次状态不可以再修改为PENDING
      batchStatus === IBatchTasksStatus.PENDING ||
      // 已取消后只能修改为已完成状态(可能任务已经全部启动运行了,即使点击了取消,仍然会继续执行完)
      (
        this.batchStatus === IBatchTasksStatus.CANCELED &&
        batchStatus !== IBatchTasksStatus.COMPLETED
      )
    ) {
      return false;
    }

    // 将 batchStatus 设置为最新的状态
    this.batchStatus = batchStatus;

    switch (this.batchStatus) {
      case IBatchTasksStatus.RUNNING:
        this.next();

        break;
      default:
    }

    this.handleProgress();

    return true;
  };

  private handleTasksStatusChange = () => {
    // 从后向前遍历,提升效率
    // FIXME ESlint 不让使用 findLast 和 findLastIndex
    const { tasks } = this.conf;
    let isCompleted = true;

    for (let i = tasks.length - 1; i >= 0; i--) {
      if (!tasks[i].isCompleted()) {
        isCompleted = false;

        break;
      }
    }

    if (isCompleted) {
      this.setBatchStatus(IBatchTasksStatus.COMPLETED);
    } else {
      this.handleProgress();
    }
  };

  private handleTaskStatusChange = (status: ETaskStatus) => {
    switch (status) {
      case ETaskStatus.RUNNING:
        this.runningCount++;

        break;
      case ETaskStatus.FAILED:
      case ETaskStatus.SUCCESSFUL:
        this.runningCount--;

        break;
      default:
        break;
    }

    this.handleTasksStatusChange();
  };

  // 
  private next = () => {
    // 判断是否等于running、是否超出最大并发
    if (this.batchStatus !== IBatchTasksStatus.RUNNING ||
      this.runningCount >= (this.conf.maxRunningCount ?? 1)) {
      return;
    }

    // 没有任务可执行了
    if (this.conf.tasks.length === 0) {
      this.setBatchStatus(IBatchTasksStatus.COMPLETED);

      return;
    }


    // 找到 pending 的第一个任务
    const task = this.conf.tasks.find(item => item.getStatus() === ETaskStatus.PENDING);

    if (!task) {
      return;
    }

    // 执行 task 的 run 方法
    // 1、handleTaskStatusChange 记录 runningCount
    // 2、this.next 执行完当前,继续执行
    task.run(this.handleTaskStatusChange).finally(this.next);
    
    // 为了继续并发判断
    this.next();
  };

  getBatchStatus = (): IBatchTasksStatus => {
    return this.batchStatus;
  };

  getTasks = (): T[] => {
    return this.conf.tasks;
  };

  statistic = (): IStatisticTasksStatusResult => {
    let pendingCount = 0;
    let runningCount = 0;
    let successfulCount = 0;
    let failedCount = 0;

    this.conf.tasks.forEach(item => {
      switch (item.getStatus()) {
        case ETaskStatus.PENDING:
          pendingCount++;

          break;
        case ETaskStatus.RUNNING:
          runningCount++;

          break;
        case ETaskStatus.SUCCESSFUL:
          successfulCount++;

          break;
        case ETaskStatus.FAILED:
          failedCount++;

          break;
        default:
      }
    });

    return {
      pendingCount,
      runningCount,
      successfulCount,
      failedCount
    };
  };

  /**
   * 继续执行任务
   */
  resume = (): boolean => {
    return this.setBatchStatus(IBatchTasksStatus.RUNNING);
  };

  /**
   * 暂停执行任务
   */
  pause = (): boolean => {
    return this.setBatchStatus(IBatchTasksStatus.PAUSED);
  };

  /**
   * 取消任务
   */
  cancel = (): boolean => {
    return this.setBatchStatus(IBatchTasksStatus.CANCELED);
  };

  /**
   * 通过ID查找并运行单个任务,不考虑批次状态以及累计任务数量
   * 适用于运行失败的任务重新运行
   */
  runById = async (id: symbol, ignoreIfSuccessful?: boolean) => {
    const task = this.conf.tasks.find(item => item.getId() === id);

    if (task) {
      return this.runTask(task, ignoreIfSuccessful);
    }

    throw new TaskNotFoundError();
  };

  /**
   * 运行单个任务,不考虑批次状态以及累计任务数量
   * 适用于运行失败的任务重新运行
   */
  runTask = async <TT extends ITask>(task: TT, ignoreIfSuccessful = true): Promise<TT> => {
    if (ignoreIfSuccessful && task.getStatus() === ETaskStatus.SUCCESSFUL) {
      return task;
    }

    if (this.conf.tasks.some(item => item.getId() === task.getId())) {
      await task.run(this.handleTaskStatusChange);
    } else {
      await task.run();
    }

    return task;
  };

  /**
   * 执行任务,并通过Promise等待所有任务完成
   */
  run = async (): Promise<T[]> => {
    if (!this.cancelablePromise) {
      this.cancelablePromise = new CancelablePromise();
    }

    try {
      this.resume();
      this.checkBatchStatusForWaitPromise();

      return await this.cancelablePromise.promise;
    } catch (err) {
      this.cancelablePromise.reject(err);

      throw err;
    } finally {
      this.cancelablePromise = undefined;
    }
  };
}