Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/yiminghe/concurrent-runner
run cancelable async function concurrently by concurrency and priority using heap
https://github.com/yiminghe/concurrent-runner
async cancelable concurrency priority promise
Last synced: 9 days ago
JSON representation
run cancelable async function concurrently by concurrency and priority using heap
- Host: GitHub
- URL: https://github.com/yiminghe/concurrent-runner
- Owner: yiminghe
- License: mit
- Created: 2022-01-26T08:50:34.000Z (almost 3 years ago)
- Default Branch: main
- Last Pushed: 2022-08-05T18:33:06.000Z (over 2 years ago)
- Last Synced: 2024-04-15T04:24:44.337Z (7 months ago)
- Topics: async, cancelable, concurrency, priority, promise
- Language: TypeScript
- Homepage:
- Size: 176 KB
- Stars: 2
- Watchers: 3
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# concurrent-runner
[![NPM version](https://badge.fury.io/js/concurrent-runner.png)](http://badge.fury.io/js/concurrent-runner)
[![NPM downloads](http://img.shields.io/npm/dm/concurrent-runner.svg)](https://npmjs.org/package/concurrent-runner)
[![Build Status](https://app.travis-ci.com/yiminghe/concurrent-runner.svg?branch=main)](https://app.travis-ci.com/github/yiminghe/concurrent-runner)run cancelable async function concurrently by concurrency and priority using heap
## API
```ts
export interface Task {
run: () => {
promise: Promise;
cancel?: () => void;
};
}export type ExtractTaskResult = T extends Task ? U : never;
/**
* -1 means higher priority
*/
export type Comparator = (t1: T, t2: T) => -1 | 0 | 1;export type TaskHandle = {
promise: Promise;
cancel: () => void;
};export interface RunnerOptions {
concurrency: number;
comparator: Comparator;
onEmpty?: () => void;
onTaskStart?: (o: {
task: T;
}) => void;
onTaskEnd?: (o: {
task: T;
result: any;
}) => void;
}export default class CocurrentRunner {
private options;
private heap;
private running;
private started;
private paused;
constructor(options: RunnerOptions);
setOptions(options: Partial>): void;
start(): void;
pause(): void;
resume(): void;
stop(): void;
private endAndSchedule;
private checkAndSchedule;
addTask>(task: TT): TaskHandle;
}
```## Usage
```ts
import CoRunner, { Task } from 'concurrent-runner';describe("concurrent runner", () => {
interface TimeTask extends Task {
time: number;
}
function timeout(d: number): Promise {
return new Promise((resolve) => {
setTimeout(() => {
resolve(d);
}, d);
});
}
function eq(r1: number, r3: number) {
return Math.abs(r1 - r3) < 20;
}function getRunner() {
const r = new CoRunner({
concurrency: 2,
comparator(t: TimeTask, t2: TimeTask) {
return t.time === t2.time ? 0 : t.time > t2.time ? 1 : -1;
},
});
return r;
}
function getRunnerWithTasks() {
const r = getRunner();
const ret: number[][] = [];
const taskHandles = [];
const times = [300, 100, 500, 100];
for (let time = 0; time < times.length; time++) {
const p = r.addTask({
run() {
return {
promise: timeout(times[time]),
};
},
time,
});
taskHandles.push(p);
p.promise.then(
(r) => {
ret.push([time, r]);
},
(r) => {
ret.push([time, r]);
}
);
}
return { r, ret, taskHandles };
}it("works for concurrency", (done) => {
const { r, ret } = getRunnerWithTasks();
const startTime: number[][] = [];
const start = Date.now();r.setOptions({
onTaskStart(info) {
startTime.push([info.task.time, Date.now() - start]);
},
onEmpty() {
expect(eq(startTime[2][1], 100)).toBe(true);
expect(eq(startTime[3][1], 300)).toBe(true);
expect(startTime.map((r) => r[0])).toEqual([0, 1, 2, 3]);
expect(ret.map((r) => r[0])).toEqual([1, 0, 3, 2]);done();
},
});r.start();
});it("can cancel running", (done) => {
const { r, ret, taskHandles } = getRunnerWithTasks();
const startTime: number[][] = [];
const start = Date.now();setTimeout(() => {
taskHandles[2].cancel();
}, 200);r.setOptions({
onTaskStart(info) {
startTime.push([info.task.time, Date.now() - start]);
},
onEmpty() {
expect(eq(startTime[2][1], 100)).toBe(true);
expect(eq(startTime[3][1], 200)).toBe(true);
expect(startTime.map((r) => r[0])).toEqual([0, 1, 2, 3]);
expect(ret.map((r) => r[0])).toEqual([1, 2, 0, 3]);
expect((ret[1][1] as any).name).toEqual("ConcurrentRunnerAbortError");
done();
},
});r.start();
});it("can cancel waiting", (done) => {
const { r, ret, taskHandles } = getRunnerWithTasks();
const startTime: number[][] = [];
const start = Date.now();setTimeout(() => {
taskHandles[3].cancel();
}, 200);r.setOptions({
onTaskStart(info) {
startTime.push([info.task.time, Date.now() - start]);
},
onEmpty() {
expect(eq(startTime[2][1], 100)).toBe(true);
expect(startTime.map((r) => r[0])).toEqual([0, 1, 2]);
expect(ret.map((r) => r[0])).toEqual([1, 3, 0, 2]);
expect((ret[1][1] as any).name).toEqual("ConcurrentRunnerAbortError");
done();
},
});r.start();
});function runWithCancel(fn: (...args: any) => Generator, ...args: any[]): { promise: Promise; cancel: () => void; } {
const gen = fn(...args);
let cancelled: boolean, cancel: () => void = function () { };
const promise = new Promise((resolve, reject) => {
cancel = () => {
cancelled = true;
};onFulfilled();
function onFulfilled(res?: any) {
if (!cancelled) {
let result: any;
try {
result = gen.next(res);
} catch (e) {
return reject(e);
}
next(result);
return null;
}
}function onRejected(err: any) {
var result: any;
try {
result = gen.throw(err);
} catch (e) {
return reject(e);
}
next(result);
}function next({ done, value }: any) {
if (done) {
return resolve(value);
}
return value.then(onFulfilled, onRejected);
}
});return { promise, cancel };
}function getCancelableRunner() {
const r = getRunner();
const ret: any[] = [];
const ret2: any[] = [];const p = r.addTask({
run() {
return runWithCancel(function* r() {
ret2.push(1);
yield timeout(200);
ret2.push(2);
yield timeout(300);
ret2.push(3);
return 4;
});
},
time: 1,
});
p.promise.then(
(r) => {
ret.push(r);
},
(r) => {
ret.push(r);
}
);
return { r, p,ret, ret2 };
}it('do not call cancel', (done) => {
const { r, ret, ret2 } = getCancelableRunner();
r.setOptions({
onEmpty() {
expect(ret).toEqual([4]);
expect(ret2).toEqual([1,2,3]);
done();
}
})
r.start();
});it('call task cancel', (done) => {
const { r,p, ret, ret2 } = getCancelableRunner();
r.setOptions({
onEmpty() {
expect(ret.length).toEqual(1);
expect(ret[0].name).toEqual('ConcurrentRunnerAbortError');
expect(ret2).toEqual([1,2]);
done();
}
})
r.start();
setTimeout(() => {
p.cancel();
}, 300);
});
});
```## HISTORY
### 0.2.0 - 2022/02/10
- change CancelablePromise return type to TaskHandle