|
|
|
@ -1,4 +1,5 @@
|
|
|
|
|
import Emittery from "emittery";
|
|
|
|
|
import { promisify } from "util";
|
|
|
|
|
|
|
|
|
|
//needed to pass information on wether or not the promise has been aborted witout 'this'
|
|
|
|
|
class AbortableEmittery extends Emittery {
|
|
|
|
@ -10,33 +11,45 @@ class AbortableEmittery extends Emittery {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default class AbortablePromise<T> extends Promise<T | null> {
|
|
|
|
|
export default class AbortablePromise<T> implements PromiseLike<T> {
|
|
|
|
|
private emitter: AbortableEmittery;
|
|
|
|
|
|
|
|
|
|
private result_promise: Promise<T>;
|
|
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
|
make_generator: (
|
|
|
|
|
await_or_abort: (
|
|
|
|
|
promise: Promise<any>,
|
|
|
|
|
on_abort: () => Promise<unknown>
|
|
|
|
|
) => Promise<unknown>
|
|
|
|
|
await_or_abort: <L>(
|
|
|
|
|
promise: Promise<L> | AbortablePromise<L>,
|
|
|
|
|
on_abort?: () => Promise<unknown> | void
|
|
|
|
|
) => Promise<L>
|
|
|
|
|
) => AsyncGenerator
|
|
|
|
|
) {
|
|
|
|
|
const emitter = new AbortableEmittery();
|
|
|
|
|
const await_or_abort = (
|
|
|
|
|
promise: Promise<any>,
|
|
|
|
|
on_abort: () => Promise<unknown>
|
|
|
|
|
) => {
|
|
|
|
|
|
|
|
|
|
function await_or_abort<L = any>(promise: AbortablePromise<L>): Promise<L>;
|
|
|
|
|
function await_or_abort<L = any>(
|
|
|
|
|
promise: Promise<L> | AbortablePromise<L>,
|
|
|
|
|
on_abort?: () => Promise<unknown> | void
|
|
|
|
|
): Promise<L> {
|
|
|
|
|
if (promise instanceof AbortablePromise && !on_abort) {
|
|
|
|
|
on_abort = async () => promise.abort();
|
|
|
|
|
}
|
|
|
|
|
if (on_abort === undefined) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
"on_abort is required when the first argument is not AbortablePromise"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
let resolved = false;
|
|
|
|
|
emitter.on("abort", () => {
|
|
|
|
|
if (!resolved) {
|
|
|
|
|
on_abort();
|
|
|
|
|
resolve(null);
|
|
|
|
|
(on_abort as Function)();
|
|
|
|
|
reject(null);
|
|
|
|
|
resolved = true;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
promise
|
|
|
|
|
.then((result) => {
|
|
|
|
|
.then((result: L) => {
|
|
|
|
|
if (!resolved) {
|
|
|
|
|
resolve(result);
|
|
|
|
|
resolved = true;
|
|
|
|
@ -44,10 +57,16 @@ export default class AbortablePromise<T> extends Promise<T | null> {
|
|
|
|
|
})
|
|
|
|
|
.catch(reject);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
super(async (resolve, reject) => {
|
|
|
|
|
}
|
|
|
|
|
this.result_promise = new Promise(async (resolve, reject) => {
|
|
|
|
|
let step_value;
|
|
|
|
|
const generator = make_generator(await_or_abort);
|
|
|
|
|
console.log("generator:", generator, make_generator);
|
|
|
|
|
const e = new Error();
|
|
|
|
|
if (!generator) {
|
|
|
|
|
console.error(e.stack);
|
|
|
|
|
console.log("generator:", generator);
|
|
|
|
|
}
|
|
|
|
|
do {
|
|
|
|
|
if (emitter.aborted) {
|
|
|
|
|
resolve(null);
|
|
|
|
@ -63,21 +82,84 @@ export default class AbortablePromise<T> extends Promise<T | null> {
|
|
|
|
|
abort() {
|
|
|
|
|
this.emitter.abort();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static deadlyRace<L>(promises: AbortablePromise<L>[]): Promise<L> {
|
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
|
let resolved = false;
|
|
|
|
|
const callback = (arg: L) => {
|
|
|
|
|
if (resolved) return;
|
|
|
|
|
resolved = true;
|
|
|
|
|
for (const promise of promises) {
|
|
|
|
|
console.log("one of the promises resolved! killing the others");
|
|
|
|
|
promise.abort();
|
|
|
|
|
}
|
|
|
|
|
resolve(arg);
|
|
|
|
|
};
|
|
|
|
|
for (const promise of promises) {
|
|
|
|
|
promise.then(callback);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
then<TResult1 = T, TResult2 = never>(
|
|
|
|
|
onfulfilled?:
|
|
|
|
|
| ((value: T) => TResult1 | PromiseLike<TResult1>)
|
|
|
|
|
| undefined
|
|
|
|
|
| null,
|
|
|
|
|
onrejected?:
|
|
|
|
|
| ((reason: any) => TResult2 | PromiseLike<TResult2>)
|
|
|
|
|
| undefined
|
|
|
|
|
| null
|
|
|
|
|
): PromiseLike<TResult1 | TResult2> {
|
|
|
|
|
const self = this;
|
|
|
|
|
return new AbortablePromise(async function* (await_or_abort) {
|
|
|
|
|
const ret = await await_or_abort(self.result_promise, () => {
|
|
|
|
|
self.abort();
|
|
|
|
|
});
|
|
|
|
|
yield;
|
|
|
|
|
if (onfulfilled) {
|
|
|
|
|
onfulfilled(ret);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// const sleep = promisify((timeout: number, callback: (...args: any[]) => void) =>
|
|
|
|
|
// setTimeout(callback, timeout)
|
|
|
|
|
// );
|
|
|
|
|
const sleep = promisify((timeout: number, callback: (...args: any[]) => void) =>
|
|
|
|
|
setTimeout(callback, timeout)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// const a = new AbortablePromise(async function* () {
|
|
|
|
|
// yield await sleep(1000);
|
|
|
|
|
// console.log("awaited 100");
|
|
|
|
|
// yield await sleep(2000);
|
|
|
|
|
// console.log("awaited 200");
|
|
|
|
|
// yield await sleep(3000);
|
|
|
|
|
// console.log("awaited 300");
|
|
|
|
|
// yield await sleep(1000);
|
|
|
|
|
// console.log("awaited 100");
|
|
|
|
|
// yield await sleep(2000);
|
|
|
|
|
// console.log("awaited 200");
|
|
|
|
|
// yield await sleep(3000);
|
|
|
|
|
// console.log("awaited 300");
|
|
|
|
|
// });
|
|
|
|
|
//
|
|
|
|
|
// const a = new AbortablePromise(async function* () {
|
|
|
|
|
// yield await sleep(1000);
|
|
|
|
|
// console.log("awaited 100");
|
|
|
|
|
// yield await sleep(2000);
|
|
|
|
|
// console.log("awaited 200");
|
|
|
|
|
// yield await sleep(3000);
|
|
|
|
|
// console.log("awaited 300");
|
|
|
|
|
// });
|
|
|
|
|
|
|
|
|
|
function abortableSleep(ms: number): AbortablePromise<void> {
|
|
|
|
|
return new AbortablePromise(async function* () {
|
|
|
|
|
await sleep(ms);
|
|
|
|
|
yield;
|
|
|
|
|
console.log(`Slept ${ms}.`);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const b = AbortablePromise.deadlyRace([
|
|
|
|
|
abortableSleep(1000),
|
|
|
|
|
abortableSleep(2000),
|
|
|
|
|
abortableSleep(3000),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// setTimeout(() => a.abort(), 1500);
|
|
|
|
|
|
|
|
|
|
// const b = new AbortablePromise(async function* (await_or_abort) {
|
|
|
|
|