Skip to content

Instantly share code, notes, and snippets.

@joelbinn
Created October 8, 2019 07:03
Show Gist options
  • Save joelbinn/1d9615cc13e7ad5151673683835ce892 to your computer and use it in GitHub Desktop.
Save joelbinn/1d9615cc13e7ad5151673683835ce892 to your computer and use it in GitHub Desktop.
A typescript implementation of a cancellable promise
import { CancellablePromise } from './cancellable-promise'
describe('CancellablePromise', () => {
describe('when cancelling', () => {
it( 'should reject with information of this', async () => {
const p: CancellablePromise<string> = new CancellablePromise(new Promise<string>(resolve => undefined))
p.cancel()
await expect(p).rejects.toEqual({wasCancelled: true})
})
})
describe('when original promise resolves', () => {
it('should resolve with that result', async () => {
const p: CancellablePromise<string> = new CancellablePromise(Promise.resolve('resultat'))
await expect(p).resolves.toEqual('resultat')
})
})
describe('when original promise rejects', () => {
let p: CancellablePromise<string>
beforeEach(() => {
p = new CancellablePromise(Promise.reject('fel'))
})
it('should resolve with that error reason', async () => {
await expect(p).rejects.toEqual('fel')
})
it('should invoke catch', async () => {
let error = undefined
await p.catch(e => error = e)
expect(error).toEqual('fel')
})
})
it('should invoke finally', async () => {
const p: CancellablePromise<string> = new CancellablePromise(Promise.resolve('resultat'))
let final = undefined
await p.finally(() => final = 'klar')
expect(final).toEqual('klar')
})
})
/**
* Error object thrown if the promise is cancelled.
*/
export interface Cancellation {
wasCancelled: boolean
}
/**
* A promise that can be cancelled (primarily to be used in backend calls).
*
* When the promise is cancelled it will reject with a Cancellation error.
*/
export class CancellablePromise<T> implements Promise<T> {
private static seq = 0
readonly id = CancellablePromise.seq++
private readonly promise: Promise<T>
private rejectInnerPromise?: (reason?: any) => void
private hasFinished = false
constructor(orgPromise: Promise<T>) {
this.promise = new Promise<T>((resolve, reject) => {
this.rejectInnerPromise = reject
orgPromise.then(
r => resolve(r),
e => reject(e)
).finally(() => this.hasFinished = true)
})
}
cancel() {
if (!this.hasFinished) {
// console.log('Cancel Promise:', this.id)
this.rejectInnerPromise && this.rejectInnerPromise({wasCancelled: true})
}
}
readonly [Symbol.toStringTag]: string
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult> {
return this.promise.catch(onrejected)
}
finally(onfinally?: (() => void) | undefined | null): Promise<T> {
return this.promise.finally(onfinally)
}
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2> {
return this.promise.then(onfulfilled, onrejected)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment