Possible to add a cancel method to Promise in Typescript?
TypeScript interfaces and types describe contracts. Yours are fine:
interface CancellablePromise<T> extends Promise<T> {
cancel: () => void
}
type QueryPromise = CancellablePromise<string | boolean>
You can then implement a contract as you want. Here is an example:
function runQuery(text: string): QueryPromise {
let rejectCb: (err: Error) => void
let p: Partial<QueryPromise> = new Promise<string | boolean>((resolve, reject) => {
rejectCb = reject
/* ... Here the implementation of the query ... */
});
p.cancel = () => {
/* ... Here the implementation of the aborting ... */
rejectCb(new Error("Canceled"))
}
return p as QueryPromise
}
Notices:
- The implementation of
cancel
should reject the promise; - I use
Partial<QueryPromise>
in order to add the membercancel
afterward.
A class can be created that inherites from Promise and adds an OnCancel to the executor parameter of the Promise constructor:
export class CancellablePromise<T> extends Promise<T> {
private onCancel: () => void
constructor(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void, onCancel: (cancelHandler: () => void) => void) => void) {
let onCancel: () => void;
super((rs, rj) => executor(rs, rj, (ch: () => void) => onCancel = ch));
this.onCancel = onCancel;
}
public cancel() {
if (this.onCancel) {
this.onCancel();
}
}
}
Here is an example of usage:
public static search(query: string): CancellablePromise<Region[]> {
return new CancellablePromise((resolve, reject, onCancel) => {
const request = $.ajax({
type: "GET",
url: ROOT_URL + 'api/Regions',
data: { q: query },
success: (regions) => resolve(regions),
error: (jqXHR) => reject(jqXHR.responseText),
});
onCancel(() => {
if (request.state() == "pending") request.abort();
});
});
}
As you can see, this implementation can be constructed like any other promise. It can be used wherever a promise can and it includes a cancellation method.
Here is an example cancelling the promise:
// Cancel previous search (if any)
if (this.currentSearch) {
this.currentSearch.cancel();
}
this.currentSearch = Regions.search(query);
this.currentSearch
.then((regions) => {
if (regions) {
this.setState({ isSearching: false, regions: regions });
} else {
this.setState({ isSearching: false, regions: [] });
}
})
.catch(() => this.setState({ isSearching: false, regions: [] }));