Skip to content

Instantly share code, notes, and snippets.

@pygy
Last active May 9, 2024 13:27
Show Gist options
  • Save pygy/6290f78b078e22418821b07d8d63f111 to your computer and use it in GitHub Desktop.
Save pygy/6290f78b078e22418821b07d8d63f111 to your computer and use it in GitHub Desktop.
You can already cancel ES6 Promises

The gist: by having a Promise adopt the state of a forever pending one, you can suspend its then handlers chain.

Promise.pending = Promise.race.bind(Promise, [])

let cancel

new Promise(function(fulfill, reject) {
  cancel = function() {fulfill(Promise.pending())}
  setTimeout(fulfill, 1000, 5)
}).then(console.log)

cancel() // 5 is never logged.

Also, mid-chain:

someAsyncJob().then(result => {
  if (result === 'enough') return Promise.pending()
  return moreAsyncWork()
}).then(otherResult => {
  // won't run if result was 'enough'
})

Possible implementations/API:

Taking a page fron Bluebird (but without canceling parents up the chain)

function cancellablePromise(executor) {
  let cancel
  var res = new Promise(function(fulfill, reject) {
    let handler
    function onCancel(cb) {handler = cb}
    cancel = function cancel() {
      fulfill(Promise.pending()) // adopt a forever pending state
      if (typeof handler === 'function') handler()
    }
    executor(fulfill, reject, onCancel)
  })
  res.cancel = cancel
  return res
}

Alternatively

function cancellablePromise(executor) {
  return new Promise(function(fulfill, reject) {
    function cancel() {fulfill(Promise.pending())}
    executor(fulfill, reject, cancel)
  })
}

Given all the hubub around cancellable promises, I'm sure I'm missing something...

@king-of-poppk
Copy link

let cancel

new Promise(function(resolve, reject) {
  cancel = () => reject(new PromiseCancelledError())}
  setTimeout(resolve, 1000, 5)
}).then(console.log, (error) => {
  if (!(error instanceof PromiseCancelledError)) { ... }
})

cancel() // 5 is never logged.

@alecat88
Copy link

alecat88 commented Oct 5, 2023

Seems to me that all of the above creates a memory leak, or am I wrong?

@king-of-poppk
Copy link

Seems to me that all of the above creates a memory leak, or am I wrong?

Mine just above should not leak if you discard the reference to cancel and clear the timeout once you do not need it anymore. Of course you cannot "clear the timeout" in the general case. Each async "leg" of a promised computation should have a way to test whether it should stop instead of continuing the promise callback chain and each truly async operation (like fetch or setTimeout) should have a real "abort" method that interrupts the request (through AbortController/clearTimeout). It seems everything can be centralized as is done in the solutions above but it still requires each "leg" to be connected to that central piece of information.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment