@clownlee/queue
Version:
JS 前端多批次,多任务队列
156 lines (141 loc) • 4.88 kB
text/typescript
import { single } from "./single";
export const sleep = (delay: number) => new Promise((resolve) => {
const st = setTimeout(() => {
clearTimeout(st);
return resolve(null);
}, delay);
});
class QueueClass {
private _status: QueueStatus = 'idle';
private _tasks: QueueTask[] = [];
private _config: QueueConfig = {
interval: 1000, // default interval
position: 'after' // default position
};
constructor(config: QueueConfig = {
interval: 1000,
position: 'after'
}) {
if (!config?.interval) {
throw new Error('interval is required');
}
if (Object.prototype.toString.call(config.interval) !== '[object Number]') {
throw new Error('interval must be a number');
}
this._config = config;
}
reExecute (fns: Function[], fnEnd: Function = () => {}, allEnd: Function = () => {}) {
this._status = 'idle';
this.execute(fns, fnEnd, allEnd);
}
termination () {
this._status = 'termination';
}
async doTask (fn: Function, fnEnd: Function = () => {}, fnIndex: number = 0) {
const iter = this._tasks.filter(item=>item.status === 'pending')[Symbol.iterator]();
while (true) {
if (['termination'].includes(this._status)) {
break;
}
const { value, done } = iter.next();
if (done) {
if (this._tasks.filter(item => ['finished', 'failed'].includes(item.status)).length !== this._tasks.length) {
await this.doTask(fn, fnEnd, fnIndex);
} else {
(fnEnd && Object.prototype.toString.call(fnEnd) === '[object Function]')&& fnEnd(this._tasks, fnIndex);
}
break;
}
if (value) {
this._status = 'running';
if (this._config.position === 'before') {
fn(value);
}
await sleep(this._config.interval);
if (this._config.position === 'after') {
fn(value);
}
}
}
}
execute (fns: Function[], fnEnd: Function = () => {}, allEnd: Function = () => {}) {
if (Object.prototype.toString.call(fns) !== '[object Array]') {
throw new Error('fns must be an array');
}
if (fns.length === 0) {
throw new Error('fns must be not empty');
}
if (fns.some(fn => Object.prototype.toString.call(fn) !== '[object Function]')) {
throw new Error('fns must be an array of functions');
}
const iter = fns[Symbol.iterator]();
(async () => {
let fnIndex = 0;
while (true) {
if (['termination'].includes(this._status)) {
break;
}
const { value, done } = iter.next();
if (done) {
this._status = 'idle';
fnIndex = 0;
(allEnd && Object.prototype.toString.call(allEnd) === '[object Function]') && allEnd(this._tasks);
break;
}
this._tasks = this._tasks.map((item) => {
if (['finished'].includes(item.status)) {
item.status = 'pending';
}
return item;
});
await this.doTask(value, fnEnd, fnIndex);
fnIndex++;
}
})()
}
resetOneTasks (task: QueueTask, status: QueueTaskStatus = 'finished') {
this._tasks = this._tasks.map(one => {
if (one.id === task.id) {
one = {
...one,
status: status,
params: task.params
};
}
return one;
});
}
get status () {
return this._status;
}
set status (status: QueueStatus) {
if (['idle', 'running', 'termination'].includes(status)) {
this._status = status;
}
}
set tasks(tasks: QueueTask[]) {
if (Object.prototype.toString.call(tasks) !== '[object Array]') {
throw new Error('tasks must be an array');
}
if (!tasks.every((task) => Object.prototype.toString.call(task) === '[object Object]')) {
throw new Error('tasks must be an array of objects');
}
if (!tasks.every((task) => 'id' in task && 'status' in task && 'params' in task)) {
throw new Error('tasks must be an array of objects with id, status and params properties');
}
if (!tasks.every((task) => (
Object.prototype.toString.call(task.id) === '[object String]' ||
Object.prototype.toString.call(task.id) === '[object Symbol]') ||
Object.prototype.toString.call(task.status) === '[object String]' ||
Object.prototype.toString.call(task.params) === '[object Object]'
)) {
throw new Error('tasks must be an array of objects with id property of type string or symbol, status property of type string and params property of type object');
}
this._tasks = tasks;
}
get tasks () {
return this._tasks;
}
}
export type Queue = QueueClass;
export const Queue = single(QueueClass);