import { EventEmitter } from "events" export class CancellationToken extends EventEmitter { private parentCancelHandler: (() => any) | null = null private _cancelled: boolean get cancelled(): boolean { return this._cancelled || (this._parent != null && this._parent.cancelled) } private _parent: CancellationToken | null = null set parent(value: CancellationToken) { this.removeParentCancelHandler() this._parent = value this.parentCancelHandler = () => this.cancel() this._parent.onCancel(this.parentCancelHandler) } // babel cannot compile ... correctly for super calls constructor(parent?: CancellationToken) { super() this._cancelled = false if (parent != null) { this.parent = parent } } cancel() { this._cancelled = true this.emit("cancel") } private onCancel(handler: () => any) { if (this.cancelled) { handler() } else { this.once("cancel", handler) } } createPromise( callback: (resolve: (thenableOrResult: R | PromiseLike) => void, reject: (error: Error) => void, onCancel: (callback: () => void) => void) => void ): Promise { if (this.cancelled) { return Promise.reject(new CancellationError()) } const finallyHandler = () => { if (cancelHandler != null) { try { this.removeListener("cancel", cancelHandler) cancelHandler = null } catch (_ignore) { // ignore } } } let cancelHandler: (() => void) | null = null return new Promise((resolve, reject) => { let addedCancelHandler: (() => void) | null = null cancelHandler = () => { try { if (addedCancelHandler != null) { addedCancelHandler() addedCancelHandler = null } } finally { reject(new CancellationError()) } } if (this.cancelled) { cancelHandler() return } this.onCancel(cancelHandler) callback(resolve, reject, (callback: () => void) => { addedCancelHandler = callback }) }) .then(it => { finallyHandler() return it }) .catch((e: any) => { finallyHandler() throw e }) } private removeParentCancelHandler() { const parent = this._parent if (parent != null && this.parentCancelHandler != null) { parent.removeListener("cancel", this.parentCancelHandler) this.parentCancelHandler = null } } dispose() { try { this.removeParentCancelHandler() } finally { this.removeAllListeners() this._parent = null } } } export class CancellationError extends Error { constructor() { super("cancelled") } }