DEV Community

Luka Vidaković
Luka Vidaković

Posted on

2

Cancelable Promise util

I found myself using this code and some derivatives time and time again so I decided to share. It's not written by me, and I found it on Edd Mann's blog. There are a few npm libraries that deal with promise cancellation in a similar manner, but I somehow prefer to have this few lines of source code somewhere in my util functions.

Here is the original function that wraps the native Promise and keeps a flag variable to allow us to cancel the .then chain anytime we want. Unfortunately the Promises themselves can't really be canceled.

const cancelable = (promise) => {
  let hasCancelled = false;

  return {
    promise: promise.then(v => {
      if (hasCancelled) {
        throw { isCancelled: true };
      }

      return v;
    }),
    cancel: () => hasCancelled = true
  }
};
Enter fullscreen mode Exit fullscreen mode

When we call the cancelable function by giving it a promise we'll get an object that has a:

  • promise property, original promise extended with a single .then handler that is able to cancel out all the following .then handlers appended to it later on. Cancellation is based on a local variable hasCancelled. If the flag turns to true before the promise resolves it throws with additional information and bypasses all the latter .then handlers. We should use this property instead of our original promise and append any required .then handlers to it.
  • cancel method that changes the local hasCancelled flag to true

Usage example:

// mocked fetch function to simulate waiting for a result 10 seconds
const fetchResult = () => new Promise(resolve => {
  setTimeout(() => resolve('response'), 10000)
})

const {promise: result, cancel} = cancelable(fetchResult())

result.catch(error => {
  if (error.isCancelled) console.log('Promise chain cancelled!')
})
result.then(res => console.log(`Handler 1: ${res}`))
result.then(res => console.log(`Handler 2: ${res}`))
      .then(res => console.log(`Handler 3: ${res}`))

// at any point in time we can cancel all of the above success handlers by using cancel function
// catch handler can verify if cancellation is the reason of failure and do something based on it, in this case log out "Promise chain cancelled!"
cancel()
Enter fullscreen mode Exit fullscreen mode

It's important to note that by using this approach we can't cancel out any handlers that were attached directly to an original promise object passed to our util function. This mechanism is only able to cancel out .then handlers appended to the returned promise. A bit weird but it's not bad once you get used to it. You can still hold a reference to both the original and derived promise.

Another note is that error with isCancelled flag ends up in the catch handler only when the original Promise eventually resolves. All of this is essentially a way for us to say: once and if this Promise resolves, skip the success handlers because we are not interested in handling this data anymore.

I'm curious to hear from you of another similar approaches you might have and why they are better/worse 🍺

Top comments (0)

typescript

11 Tips That Make You a Better Typescript Programmer

1 Think in {Set}

Type is an everyday concept to programmers, but it’s surprisingly difficult to define it succinctly. I find it helpful to use Set as a conceptual model instead.

#2 Understand declared type and narrowed type

One extremely powerful typescript feature is automatic type narrowing based on control flow. This means a variable has two types associated with it at any specific point of code location: a declaration type and a narrowed type.

#3 Use discriminated union instead of optional fields

...

Read the whole post now!

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay