0

With an Array of Promises how do I wait until they have all resolved before proceeding?

Below is an example implementation, where task_2 is firing it's rejection before task_1 can trigger.

const $output = $$('output')

const config = {
  task_1: [
    val => new Promise((resolve, reject) => setTimeout(() => reject('Awaited error'), 2000)),
    val => new Promise((resolve, reject) => resolve(val))
  ],
  task_2: [
    val => new Promise((resolve, reject) => resolve(val)),
    val => new Promise((resolve, reject) => reject('An error')),
  ]
}

taskRunner({
    task_1: 'task parameters',
    task_2: 'task parameters'
  })
  .then(res => $output.text(res).fadeIn())
  .catch(err => $output.text(err).fadeIn())

function taskRunner(tasks) {
  return new Promise((resolve, reject) => {
    let arr = []
    for (const task in tasks) {
      if (!config.hasOwnProperty(task)) return reject(`${task} has no tasks`)
      arr.push(config[task].map((cb => cb(tasks[task]))))
    }

    const toRun = arr.reduce((a, b) => a.concat(b))
    Promise.all(toRun)
      .then(res => resolve(res))
      .catch(err => reject(err))
  })
}

function $$(data) {
  return $('[data-' + data + ']')
}
html, body { margin: 0; padding: 0 }
div.output {
  font-family: 'Courier', monospace;
  color: #383838;
  margin: 15px;
  padding: 15px 20px;
  background: #fafafa;
  border-radius: 3px;
  border-bottom: 1px solid #dadada;
}
.js-hide { display: none }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="output js-hide" data-output></div>
stackunderflow
  • 1,644
  • 6
  • 24
  • 40
  • 1
    What is the question? What isn't behaving as expected? There really is no need to create a new promise in your taskrunner, you can simply `return Promise.all(..)` – Daniel B Dec 21 '16 at 14:45
  • According to docs Promise All is rejected if any one of the elements is rejected: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all – J_Everhart383 Dec 21 '16 at 14:46
  • @DanielB The first task is not being rejected first, secondly how could I handle the rejection of a task not existing within the `config` by just returning `promise.all()`? – stackunderflow Dec 21 '16 at 14:49
  • @J_Everhart383 Could you suggest an alternative? – stackunderflow Dec 21 '16 at 14:49
  • @KarlBateman it seems like Promise.All is what you are looking for. I think you just need to address the reason why task 2 is being rejected. Here is another built-in method for use with an iterable, but I'm not sure it is what you are looking for either: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race – J_Everhart383 Dec 21 '16 at 14:58
  • Might also help if there were some context for the implementation. I get it's some sort of task manager. – J_Everhart383 Dec 21 '16 at 14:59
  • @J_Everhart383 It's actually just an example, I am curious as to how you would wait for an array of Promises to resolve/reject, before proceeding. You might have for example an array of Promises which make HTTP calls, in the event of an error, you would want to output all errors rather than the most recent. – stackunderflow Dec 21 '16 at 15:03
  • Sending you a chat w/ a response. SO is asking me to move out of comments. – J_Everhart383 Dec 21 '16 at 15:11
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/131167/discussion-between-j-everhart383-and-karl-bateman). – J_Everhart383 Dec 21 '16 at 15:12

2 Answers2

0

If I understand your question correctly from your comment

It's actually just an example, I am curious as to how you would wait for an array of Promises to resolve/reject, before proceeding. You might have for example an array of Promises which make HTTP calls, in the event of an error, you would want to output all errors rather than the most recent.

you want to wait until all the promises have either been resolved or rejected, and then continue.

Promise.all() will reject if any of the given promises in the array is rejected, thus it is not an alternative for your in this case.

There is an .always() function in jQuery that work like this, and if you were to use BlueBird you could use .reflect() to inspect the promises of the Promise.all(). There is no current function in ES6 that can do this.

Borrowing from the answer of this question, ES6 promise settled callback?, you can implement your own version of .always()/.finally()/.allSettled() like this

Promise.prototype.finally = function(cb) {
    const res = () => this
    return this.then(value =>
        Promise.resolve(cb({state:"fulfilled", value})).then(res)
    , reason =>
        Promise.resolve(cb({state:"rejected", reason})).then(res)
    );
};
Community
  • 1
  • 1
Daniel B
  • 8,770
  • 5
  • 43
  • 76
0

If i understand your problem correctly i guess you might still use Promise.all() however you have to wrap your promises by a function to handle the rejections separately. I mean you may catch the rejection before it triggers a premature exit of the Promise.all() and handle the rejection by resolving it with a special object.

As an example;

function handleRejectedPromises(p){
  return new Promise((resolve,reject) => p.then(v => resolve({status: "resolved", value: v}))
                                          .catch(x => resolve({status: "rejected", reason: x})));
}
var ps = [Promise.resolve(42), Promise.reject(37), Promise.resolve(23), Promise.resolve(11)];

Promise.all(ps.map(p => handleRejectedPromises(p)))
       .then(ra => ra.forEach(r => console.log(r)));

OK as per @Bergi's comment i have to correct my answer accordingly in order to take it off of anti-pattern realm.

function handleRejectedPromises(p){
  return p.then(v => ({status: "resolved", value: v}))
          .catch(x => ({status: "rejected", reason: x}));
}
var ps = [Promise.resolve(42), Promise.reject(37), Promise.resolve(23), Promise.resolve(11)];

Promise.all(ps.map(p => handleRejectedPromises(p)))
       .then(ra => ra.forEach(r => console.log(r)));
Redu
  • 25,060
  • 6
  • 56
  • 76
  • 1
    Avoid the [`Promise` constructor antipattern](http://stackoverflow.com/q/23803743/1048572?What-is-the-promise-construction-antipattern-and-how-to-avoid-it)! – Bergi Dec 21 '16 at 16:52
  • @Bergi I agree i also don't like to carry promise construction inside the promises yet... No i don't think this is an anti-pattern as per this topic is concerned. I believe this is the way this task has to be accomplished within the available boundaries of native JS promises. Sometimes there are things one has to invent that might seem unorthodox just like inverting resolve and reject in `Promise.all()` in order to get the first resolving promise without getting caught to the rejecting ones. This is just one of them. – Redu Dec 21 '16 at 20:05
  • 2
    It definitely is an antipattern. That whole `new Promise` construct should be simply replaced by the equivalent (sans your bugs) and much simpler `p.then(v => ({status:"resolved",value:v}), x => ({status:"rejected",reason:x}))`. – Bergi Dec 21 '16 at 20:59
  • 1
    @Bergi OK i got it. Thanks for your correction. – Redu Dec 21 '16 at 21:22