Promise - is it possible to force cancel a promise
In modern JavaScript - no
Promises have settled (hah) and it appears like it will never be possible to cancel a (pending) promise.
Instead, there is a cross-platform (Node, Browsers etc) cancellation primitive as part of WHATWG (a standards body that also builds HTML) called AbortController
. You can use it to cancel functions that return promises rather than promises themselves:
// Take a signal parameter in the function that needs cancellation
async function somethingIWantToCancel({ signal } = {}) {
// either pass it directly to APIs that support it
// (fetch and most Node APIs do)
const response = await fetch('.../', { signal });
// return response.json;
// or if the API does not already support it -
// manually adapt your code to support signals:
const onAbort = (e) => {
// run any code relating to aborting here
};
signal.addEventListener('abort', onAbort, { once: true });
// and be sure to clean it up when the action you are performing
// is finished to avoid a leak
// ... sometime later ...
signal.removeEventListener('abort', onAbort);
}
// Usage
const ac = new AbortController();
setTimeout(() => ac.abort(), 1000); // give it a 1s timeout
try {
await somethingIWantToCancel({ signal: ac.signal });
} catch (e) {
if (e.name === 'AbortError') {
// deal with cancellation in caller, or ignore
} else {
throw e; // don't swallow errors :)
}
}
No. We can't do that yet.
ES6 promises do not support cancellation yet. It's on its way, and its design is something a lot of people worked really hard on. Sound cancellation semantics are hard to get right and this is work in progress. There are interesting debates on the "fetch" repo, on esdiscuss and on several other repos on GH but I'd just be patient if I were you.
But, but, but.. cancellation is really important!
It is, the reality of the matter is cancellation is really an important scenario in client-side programming. The cases you describe like aborting web requests are important and they're everywhere.
So... the language screwed me!
Yeah, sorry about that. Promises had to get in first before further things were specified - so they went in without some useful stuff like .finally
and .cancel
- it's on its way though, to the spec through the DOM. Cancellation is not an afterthought it's just a time constraint and a more iterative approach to API design.
So what can I do?
You have several alternatives:
- Use a third party library like bluebird who can move a lot faster than the spec and thus have cancellation as well as a bunch of other goodies - this is what large companies like WhatsApp do.
- Pass a cancellation token.
Using a third party library is pretty obvious. As for a token, you can make your method take a function in and then call it, as such:
function getWithCancel(url, token) { // the token is for cancellation
var xhr = new XMLHttpRequest;
xhr.open("GET", url);
return new Promise(function(resolve, reject) {
xhr.onload = function() { resolve(xhr.responseText); });
token.cancel = function() { // SPECIFY CANCELLATION
xhr.abort(); // abort request
reject(new Error("Cancelled")); // reject the promise
};
xhr.onerror = reject;
});
};
Which would let you do:
var token = {};
var promise = getWithCancel("/someUrl", token);
// later we want to abort the promise:
token.cancel();
Your actual use case - last
This isn't too hard with the token approach:
function last(fn) {
var lastToken = { cancel: function(){} }; // start with no op
return function() {
lastToken.cancel();
var args = Array.prototype.slice.call(arguments);
args.push(lastToken);
return fn.apply(this, args);
};
}
Which would let you do:
var synced = last(getWithCancel);
synced("/url1?q=a"); // this will get canceled
synced("/url1?q=ab"); // this will get canceled too
synced("/url1?q=abc"); // this will get canceled too
synced("/url1?q=abcd").then(function() {
// only this will run
});
And no, libraries like Bacon and Rx don't "shine" here because they're observable libraries, they just have the same advantage user level promise libraries have by not being spec bound. I guess we'll wait to have and see in ES2016 when observables go native. They are nifty for typeahead though.
Cancel a vanilla ECMAScript 6 Promise chain
Is there a method for clearing the
.then
s of a JavaScript Promise instance?
No. Not in ECMAScript 6 at least. Promises (and their then
handlers) are uncancellable by default (unfortunately). There is a bit of discussion on es-discuss (e.g. here) about how to do this in the right way, but whatever approach will win it won't land in ES6.
The current standpoint is that subclassing will allow to create cancellable promises using your own implementation (not sure how well that'll work).
Until the language commitee has figured out the best way (ES7 hopefully?) you can still use userland Promise implementations, many of which feature cancellation.
Current discussion is in the https://github.com/domenic/cancelable-promise and https://github.com/bergus/promise-cancellation drafts.
How to cancel last Promise if not resolved?
Promises aren't cancelable as such, but are cancelled in a limited sense by causing them to be rejected.
With that in mind, cancellation can be achieved with a small amount of elaboration around Promise.race()
and the promise-returning function you wish to be cancelable.
function makeCancellable(fn) {
var reject_; // cache for the latest `reject` executable
return function(...params) {
if(reject_) reject_(new Error('_cancelled_')); // If previous reject_ exists, cancel it.
// Note, this has an effect only if the previous race is still pending.
let canceller = new Promise((resolve, reject) => { // create canceller promise
reject_ = reject; // cache the canceller's `reject` executable
});
return Promise.race([canceller, fn.apply(null, params)]); // now race the promise of interest against the canceller
}
}
Assuming your http call function is named httpRequest
(promise
is confusing):
const search = makeCancellable(httpRequest);
Now, each time search()
is called, the cached reject
executable is called to "cancel" the preceding search (if it exists and its race has not already fulfilled).
// Search 1: straightforward - nothing to cancel - httpRequest(200) is called
search(200)
.then(function() { console.log('search1 resolved') })
.catch(function(err) { console.log('search3 rejected', err) });
// Search 2: search 1 is cancelled and its catch callback fires - httpRequest(2000) is called
search(2000)
.then(function() { console.log('search2 resolved') })
.catch(function(err) { console.log('search3 rejected', err) });
// Search 3: search 2 is cancelled and its catch callback fires - httpRequest(1000) is called
search(1000)
.then(function() { console.log('search3 resolved') })
.catch(function(err) { console.log('search3 rejected', err) });
If necessary, the catch callbacks can test err.message === '_cancelled_'
in order to distinguish between cancellation and other causes of rejection.
Cancel JavaScript Promise
You’ve assigned a rejected promise to a variable, passed the rejected promise into your function, and assigned a resolved promise to the variable. The two values the variable takes on are unrelated, and you can’t change the state of a settled promise.
Pass in a promise you can resolve:
let cancel;
let cancelPromise = new Promise((resolve) => {
cancel = resolve;
});
example(cancelPromise).then(…).catch(…);
console.log('attempting cancellation of promise');
cancel();
function example(cancel = Promise.reject()) { return new Promise((resolve, reject) => { const timer = setTimeout(() => resolve('jack-jack'), 5000); cancel.then((res) => { clearTimeout(timer); reject('cancelled'); }, ()=>{}) });}
let cancel;let cancelPromise = new Promise((resolve) => { cancel = resolve;});
example(cancelPromise) .then((res) => console.log('res handled:' + res)) .catch((err) => console.log('err handled:' + err));console.log('attempting cancellation of promise');cancel();
Stop other promises when Promise.all() rejects
Cancellation of promises is not included in the Promises/A+ specification.
However, some Promise libraries have such a cancellation extension. Take for example bluebird:
Promise.config({ cancellation: true }); // <-- enables this non-standard feature
const promise1 = new Promise((resolve, reject) => { setTimeout(resolve, 1000, 'resolve1');}).then(a => { console.log('then1'); return a; });
const promise2 = new Promise((resolve, reject) => { setTimeout(reject, 2000, 'reject2');}).then(a => { console.log('then2'); return a; });
const promise3 = new Promise((resolve, reject) => { setTimeout(resolve, 3000, 'resolve3');}).then(a => { console.log('then3'); return a; });
const promises = [promise1, promise2, promise3];
Promise.all(promises) .then(values => { console.log('then', values); }) .catch(err => { console.log('catch', err); promises.forEach(p => p.cancel()); // <--- Does not work with standard promises });
<script src="https://cdn.jsdelivr.net/bluebird/latest/bluebird.core.min.js"></script>
PromiseKit cancel a promise
In PromiseKit 7, use func cancellize()
to convert a Promise
or Guarantee
into a promise that can be cancelled:
currentOperation = client.load(skip: skip, query: nil)
let currentOperationCancellable = currentOperation!.then { (items) in
self.processItems(items: items, skip: skip, query: query)
}.cancellize()
currentOperationCancellable.catch { (error) in
print("failed to load items - just retrying")
self.loadIfNeeded(skip: skip, query: query, onlyInStock: onlyInStock)
}
Use func cancel()
or func cancel(with:)
to cancel a cancellable Promise
or Guarantee
:
currentOperationCancellable.cancel()
currentOperationCancellable.cancel(with: NSError(domain: "", code: 0, userInfo: nil))
Related Topics
Promise.All Consumes All My Ram
How to Pass Data from a Page to Another Page Using React Router
How to Set the Value Property in Angularjs' Ng-Options
How to Check a Radio Button with Jquery
How to Get Element by Xpath Using JavaScript in Selenium Webdriver
Jquery - Setting the Selected Value of a Select Control via Its Text Description
Concrete JavaScript Regular Expression for Accented Characters (Diacritics)
How to Get the Non-Enumerable Inherited Property Names of an Object
Using Jquery's Ajax Method to Retrieve Images as a Blob
JavaScript Document.Getelementsbyclassname Compatibility with Ie
How to Order Events Bound with Jquery
Hot and Cold Observables: Are There 'Hot' and 'Cold' Operators
Is There a More Accurate Way to Create a JavaScript Timer Than Settimeout
How to Sort an Array on Multiple Columns
Open Url in New Window with JavaScript
When to Use Es6 Class Based React Components VS. Functional Es6 React Components