const toStringTag: typeof Symbol.toStringTag = typeof Symbol !== 'undefined' ? Symbol.toStringTag : ('@@toStringTag' as any); class CancelablePromiseInternal { #internals: Internals; #promise: Promise; [toStringTag] = 'CancelablePromise'; constructor({ executor = () => {}, internals = defaultInternals(), promise = new Promise((resolve, reject) => executor(resolve, reject, (onCancel) => { internals.onCancelList.push(onCancel); }) ), }: { executor?: ( resolve: (value: T | PromiseLike) => void, reject: (reason?: any) => void, onCancel: (cancelHandler: () => void) => void ) => void; internals?: Internals; promise?: Promise; }) { this.cancel = this.cancel.bind(this); this.#internals = internals; this.#promise = promise || new Promise((resolve, reject) => executor(resolve, reject, (onCancel) => { internals.onCancelList.push(onCancel); }) ); } then( onfulfilled?: | (( value: T ) => TResult1 | PromiseLike | CancelablePromise) | undefined | null, onrejected?: | (( reason: any ) => TResult2 | PromiseLike | CancelablePromise) | undefined | null ): CancelablePromise { return makeCancelable( this.#promise.then( createCallback(onfulfilled, this.#internals), createCallback(onrejected, this.#internals) ), this.#internals ); } catch( onrejected?: | (( reason: any ) => TResult | PromiseLike | CancelablePromise) | undefined | null ): CancelablePromise { return makeCancelable( this.#promise.catch(createCallback(onrejected, this.#internals)), this.#internals ); } finally( onfinally?: (() => void) | undefined | null, runWhenCanceled?: boolean ): CancelablePromise { if (runWhenCanceled) { this.#internals.onCancelList.push(onfinally); } return makeCancelable( this.#promise.finally( createCallback(() => { if (onfinally) { if (runWhenCanceled) { this.#internals.onCancelList = this.#internals.onCancelList.filter( (callback) => callback !== onfinally ); } return onfinally(); } }, this.#internals) ), this.#internals ); } cancel(): void { this.#internals.isCanceled = true; const callbacks = this.#internals.onCancelList; this.#internals.onCancelList = []; for (const callback of callbacks) { if (typeof callback === 'function') { try { callback(); } catch (err) { console.error(err); } } } } isCanceled(): boolean { return this.#internals.isCanceled === true; } } export class CancelablePromise extends CancelablePromiseInternal { static all = function all(iterable: any) { return makeAllCancelable(iterable, Promise.all(iterable)); } as CancelablePromiseOverloads['all']; static allSettled = function allSettled(iterable: any) { return makeAllCancelable(iterable, Promise.allSettled(iterable)); } as CancelablePromiseOverloads['allSettled']; static any = function any(iterable: any) { return makeAllCancelable(iterable, Promise.any(iterable)); } as CancelablePromiseOverloads['any']; static race = function race(iterable) { return makeAllCancelable(iterable, Promise.race(iterable)); } as CancelablePromiseOverloads['race']; static resolve = function resolve(value) { return cancelable(Promise.resolve(value)); } as CancelablePromiseOverloads['resolve']; static reject = function reject(reason) { return cancelable(Promise.reject(reason)); } as CancelablePromiseOverloads['reject']; static isCancelable = isCancelablePromise; constructor( executor: ( resolve: (value: T | PromiseLike) => void, reject: (reason?: any) => void, onCancel: (cancelHandler: () => void) => void ) => void ) { super({ executor }); } } export default CancelablePromise; export function cancelable(promise: Promise): CancelablePromise { return makeCancelable(promise, defaultInternals()); } export function isCancelablePromise(promise: any): boolean { return ( promise instanceof CancelablePromise || promise instanceof CancelablePromiseInternal ); } function createCallback(onResult: any, internals: Internals):any { if (onResult) { return (arg?: any) => { if (!internals.isCanceled) { const result = onResult(arg); if (isCancelablePromise(result)) { internals.onCancelList.push(result.cancel); } return result; } return arg; }; } } function makeCancelable(promise: Promise, internals: Internals) { return new CancelablePromiseInternal({ internals, promise, }) as CancelablePromise; } function makeAllCancelable(iterable: any, promise: Promise) { const internals = defaultInternals(); internals.onCancelList.push(() => { for (const resolvable of iterable) { if (isCancelablePromise(resolvable)) { resolvable.cancel(); } } }); return new CancelablePromiseInternal({ internals, promise }); } function defaultInternals(): Internals { return { isCanceled: false, onCancelList: [] }; } interface Internals { isCanceled: boolean; onCancelList: any[]; } interface CancelablePromiseOverloads { all( values: T ): CancelablePromise<{ -readonly [P in keyof T]: Awaited }>; allSettled( values: T ): CancelablePromise<{ -readonly [P in keyof T]: PromiseSettledResult>; }>; allSettled( values: Iterable | CancelablePromise> ): CancelablePromise>[]>; any( values: T ): CancelablePromise>; any( values: Iterable | CancelablePromise> ): CancelablePromise>; race( values: T ): CancelablePromise>; resolve(): CancelablePromise; resolve( value: T | PromiseLike | CancelablePromise ): CancelablePromise; reject(reason?: any): CancelablePromise; }