ES6 introduces Promise.race()
, which is absent from the $q
spec in Angular 1. Like Promise.all()
, this static method accepts an iterable collection of promise objects; whichever one resolves or rejects first will become the result of the promise wrapping the collection. This may seem like unusual behavior, but it becomes quite useful when you're building a cancellation behavior into the system.
The code, links, and a live example of this are available at .
Suppose you started with a simple promise that resolves to a value after 3 seconds:
const delayedPromise = new Promise((resolve, reject) => setTimeout(resolve.bind(null, 'foobar'), 3000)) .then(val => console.log(val));
You would like to have the ability to detach a part of your application from waiting for this promise.
A simple solution would be to expose the promise's reject handler and just invoke it from whatever is to perform the cancelation. However, it is preferable to stop waiting for this promise instead of destroying it.
A concrete example of this would be a slow but critical HTTP request that your application makes. You might not want the UI to wait for it to complete, but you may have resolve handlers attached to the request that you still want to handle the result, once it is returned.
Instead, you can take advantage of Promise.race()
and introduce a cancellation promise alongside the original one:
// Use this method to capture the cancellation function var cancel; const cancelPromise = new Promise((resolve, reject) => { cancel = reject; }); const delayedPromise = new Promise((resolve, reject) => setTimeout(resolve.bind(null, 'foobar'), 3000)); // Promise.race() creates a new promise Promise.race([cancelPromise, delayedPromise]) .then( val => console.log(val), () => console.error('cancelled!')); // If you invoke cancel() before 3 seconds elapses // (error) "cancelled!" // Instead, if 3 seconds elapses // "foobar"
Now, if delayedPromise
resolves first, the promise created by Promise.race()
will log the value passed to it here, foobar
. If, however, you invoke cancel()
before it happens, then that same Promise will print a cancelled!
error.
Promise.race()
just waits for any of its inner promises to arrive at the final state. It creates and returns a new promise that is beholden to the state of the contained promises. When it observes that any of them transitions to the final state, the new promise also assumes this state.
In this example, the executor of cancelPromise
and delayedPromise
are invoked before Promise.race()
is called. Since promises only care about the state of other promises, it isn't important that the promises passed to Promise.race()
need to be already technically started.
Note that the use of Promise.race()
doesn't affect the implementation of delayedPromise
. Even when cancel()
is invoked, delayedPromise
will still be resolved and its handlers will still be executed normally, unaware that the surrounding Promise.race()
has already been rejected. You can prove this to yourself by adding a resolve handler to delayedPromise
, invoking cancel()
and seeing the resolve handler of delayedPromise
being executed anyway:
var cancel; const cancelPromise = new Promise((resolve, reject) => { cancel = reject; }); const delayedPromise = new Promise((resolve, reject) => setTimeout(resolve.bind(null, 'foobar'), 3000)) .then(() => console.log('still resolved!')); Promise.race([ cancelPromise, delayedPromise ]) .then( val => console.log(val), () => console.error('cancelled!')); cancel(); // (error) cancelled! // After 3 seconds elapses // "still resolved!"