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'
})
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...
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 (likefetch
orsetTimeout
) should have a real "abort" method that interrupts the request (throughAbortController
/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.