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;
}
};
}