/*global assert*/ class CancellationError extends Error {} CancellationError.prototype.name = 'CancellationError' function deferred () { let _resolve, _reject const promise = new Promise((resolve, reject) => { _resolve = resolve _reject = reject }) return {promise, resolve: _resolve, reject: _reject} } function cancellable (fn) { let cancel let cancelled = false let d = deferred() const cancellation = new Promise((resolve, reject) => { d.promise.then(() => resolve(), () => resolve()) cancel = (reason) => { cancelled = true if (reason) assert.equal(typeof reason, 'string') reject(new CancellationError(reason || 'cancelled')) } }) fn(cancellation, () => cancelled).then(d.resolve, d.reject) return Object.assign(d.promise, {cancel}) } function somethingAsync () { return cancellable((cancellation, cancelled) => new Promise((resolve, reject) => { const start = Date.now() let timeout cancellation.catch((err) => { clearTimeout(timeout) console.log(`Cancelled after ${Date.now() - start}ms`) reject(err) }) timeout = setTimeout(() => { console.log(`Resolving after ${Date.now() - start}ms`) resolve('two seconds have passed') }, 2000) })) } let p = somethingAsync() p.then(console.log, console.error) p.cancel()