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 member cancel 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: [] }));

Tags:

Typescript