Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stop other promises when Promise.all() rejects

While all the questions about Promise.all focus on how to wait for all promises, I want to go the other way -- when any of the promises fails, stop the others, or even stop the whole script.

Here's a short example to illustrate:

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; });

Promise.all([promise1, promise2, promise3])
  .then(values => { console.log('then', values); })
  .catch(err => { console.log('catch', err); throw err; });

// results:
// > "then1"
// > "catch" "reject2"
// > "then3"    <------- WHY?

The script continues to resolve promise3, even though the final all(...).catch() throws! Can someone explain why? What can I do to stop the other promises at the point any of them rejects?

like image 308
quezak Avatar asked Feb 13 '18 08:02

quezak


People also ask

What happens if a Promise is rejected in Promise all?

Promise.all is rejected if any of the elements are rejected. For example, if you pass in four promises that resolve after a timeout and one promise that rejects immediately, then Promise.all will reject immediately.

How do you handle rejected promises in Promise?

You'll want to use Promise. race(). The Promise. race() method returns a promise that fulfills or rejects as soon as one of the promises in an iterable fulfills or rejects, with the value or reason from that promise.

Does Promise all stop execution?

all to Stop Async/Await from Blocking Execution in JS. When writing asynchronous code, async/await is a powerful tool — but it comes with risks! Learn how to avoid code slowdowns in this tutorial.

What happens if multiple resolve or reject is called within a Promise?

It is not safe to resolve/reject promise multiple times. It is basically a bug, that is hard to catch, becasue it can be not always reproducible.


1 Answers

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>

Note that even though promise3 is cancelled, its setTimeout callback will still be called. But it will not trigger the then or catch callbacks. It will be as if that promise never comes to a resolution ... ever.

If you want to also stop the timer event from triggering, then this is unrelated to promises, and can be done with clearTimeout. Bluebird exposes an onCancel callback function in the Promise constructor, which it will call when a promise is cancelled. So you can use that to remove the timer event:

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, onCancel) => { // Third argument (non-standard)
    var timer = setTimeout(resolve, 3000, 'resolve3');
    onCancel(_ => {
        clearTimeout(timer);
        console.log('cancelled 3');
    });
}).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>
like image 188
trincot Avatar answered Oct 05 '22 23:10

trincot