Cancel a Vanilla Ecmascript 6 Promise Chain

Cancel a vanilla ECMAScript 6 Promise chain

Is there a method for clearing the .thens 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.

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.

Create cancellable promise in ECMAScript 6

I'm getting an error saying Promise.prototype is readonly

I think this isn't true. What is the error you are receiving?

What I am wondering is, can I use this promise to cancel/abort the request made?

Instead of returning the new Promise you create, store it in a closure and return the Promise instance with an extra method that has scope to call the abort method on req and also the reject method of the Promise.

As a side note, in your first example you are attempting to call resolve within your new Promise but are not making it available by accepting the resolve and reject functions as arguments to the Promise constructor executor.

function callAPI() {
let _reject;
let _req;

let promise = new Promise(function (resolve, reject) {
_reject = reject;
_req = request.get('/some.json').end(function (error, res) {
resolve(res);
});
});

promise.abort = function () {
_req.abort();
_reject();
};

return promise;
}

let call = callAPI();

call.abort();

Abort promise chain upon action dispatch (rxjs)

I'm not 100% sure how your ofActionDispatched function is working but can you get it to throw an error if it needs to abort and then use catchError to return a null value and then check for that in the subscription, like so:

this.actions$.pipe(
map(obj => {
// do stuff
}),
catchError(err => {
return of(null);
}))
.subscribe((res) => {
if (!res) {
// do stuff if error was caught in pipe
}
})


Related Topics



Leave a reply



Submit