Javascript — Create a Cancellable Promise

Eishta Mittal
4 min readFeb 18, 2024

Promise is one of the most difficult topic in JS. It introduces asynchronous behaviour of JS and sometimes gets confusing when we start thinking about its implementation.

Here, we are going to see how to create a cancellable promise.
So, what is a cancellable promise ? A promise that can be cancelled… that’s it ?

Is there any cancel method available in a Promise Object? Let’s say we create one method, but what will it do?

So, here is the problem statement :-

Create a cancellable promise P with a method cancel such that after we call the method P.cancel(), the promise cannot be resolved now. There should be a way to know that the promise was cancelled and not rejected.

Solution

Let’s assume we have a cancellable promise cancelablePromise created by a wrapper function makeCancellablePromise that adds a cancel method and it will be used as given:-

const promise = new Promise((res, rej)=> setTimeout(res, 500, "resolved"));

// wrapper function
const makeCancellablePromise = (promise) => {
// adds a cancel method to the promise
// should return a promise as we dont want to loose the promise chaining
}

promise.then((data)=> conosle.log(data)); // logs "resolved" here

// we should be able to use it like this
const newPromise = makeCancellablePromise(promise);
newPromise.cancel();

Let’s see how we can achieve this. As we know, Promise does not have any method named cancel within its implementation, so we need to add this functionality manually.

We have two tasks here:-
1. Add method named cancel to the promise.
2. Add a way to know whether the promise was rejected because of an error or cancellation.

Add a method cancel

  1. We can add a method to the promise prototype.
    This will provide the cancel functionality to all the promises that are created using the Promise constructor function but we don’t want to add the cancelling feature to all the promise objects in an actual project.
  2. Wrap the promise inside another wrapper promise and add the method cancel only to the wrapper promise.
const makeCancellablePromise = (promise) => {
// 1. create a new Promise
const newPromise = new Promise((res, rej) => {
// 2. resolve and reject the actual promise inside the new promise
promise
.then(res)
.catch(rej);
});
// 3. add a cancel method to the new promise
newPromise.cancel = () => { ... }

// should return a promise as we dont want to loose the promise chaining
return newPromise;
}

What’s happening here?

We are creating a function which is taking a promise object and returning another promise — newPromise which has a cancel function associated with it.

How to use it ?

const cancellablePromise = makeCancellablePromise(promise);

cancellablePromise.cancel();

We can simply pass the promise to makeCancellablePromise, as a result we will get a new promise which has a cancel method attached to it.

How do we know if the promise rejected because of cancellation ?

The responsibilty of the cancel function is to reject the promise. But how do we know if the promise is cancelled. We will maintain a flag isCancelled for this purpose.

const makeCancellablePromise = (promise) => {
const isCancelled = false;
const newPromise = new Promise((res, rej) => {
// if the flag is true, proise won't resolve or reject
promise
.then((result) => !isCancelled && res(result))
.catch((err) => !isCancelled && rej(err));
});
newPromise.cancel = () => {
// setting the flag as true
isCancelled = true;
}
return newPromise;
}

Here is the complete solution:-

const promise = new Promise((res, rej) => {
setTimeout(() => rej("rejeted"), 1000);
});

const cancellablePromise = (promise) => {
const isCancelled = false;
const newPromise = new Promise((res, rej) => {
promise
.then((result) => !isCancelled && res(result))
.catch((err) => !isCancelled && rej(err));
});
newPromise.cancel = () => {
isCancelled = true;
};
return newPromise;
};

const newCancellablePromise = cancellablePromise(promise);

newCancellablePromise.then(console.log);
newCancellablePromise.catch(console.log);

// -------------------------------- USAGE ----------------------------------
// if we cancel immediately so it will not show any data as the flag is true
newCancellablePromise.cancel();

// if we cancel after sometime it will show data as till then the promise gets rejected
setTimeout(()=> newCancellablePromise.cancel(), 2000) // cancels after the promise has already resolved so it will show data

What’s missing here ?
If we look carefully, we are waiting for the promise to either fulfill or reject and checking for the flag in then or catch. But why to wait till then? Can;t we directly reject the promise when it is cancelled ?

const promise = new Promise((res, rej) => {
setTimeout(() => rej("rejected because of error"), 1000);
});

const cancellablePromise = (promise) => {
let rejectFn;
const newPromise = new Promise((res, rej) => {
rejectFn = rej;
promise
.then(res)
.catch(rej);
});
newPromise.cancel = () => {
rejectFn("the promise got cancelled");
};
return newPromise;
};


// -------------------- USAGE ---------------------------
// CANCELLING THE PROMISE
const newCancellablePromise = cancellablePromise(promise);

newCancellablePromise.then(console.log);
newCancellablePromise.catch(console.log);

setTimeout(()=> newCancellablePromise.cancel(), 200) // cancels before the resolution the promise

// output
// the promise got cancelled

// -------------------- USAGE ---------------------------
// THE PROMISE GETS REJECTED BECAUSE OF ERROR

const newCancellablePromise = cancellablePromise(promise);

newCancellablePromise.then(console.log);
newCancellablePromise.catch(console.log);

setTimeout(()=> newCancellablePromise.cancel(), 2000) // cancels before the resolution the promise

// output
// rejected because of error

Sources :-

https://medium.com/@awwfrontend/promise-with-cancel-method-cancellable-promise-b2ca8d7c154

--

--