Jorge Valvert
Jorge Valvert

Reputation: 986

How to iterate an array in javascript, with promises inside the loop block and wait for all promises to be completed to continue

I need to iterate an array in javascript with some values that will be used to call an asynchronous function that returns a promise. I can´t continue the next code section without all promises were completed.

In the following example, the function "processInvoices" has to resolve a promise until all promises inside were completed (assume that "confirmInvoice" is an asynchronous function that has different response times):

processInvoices(invoices)
{
  return new promise((resolve,reject)=>
    {
        invoices.forEach(number=> 
            {
            
                confirmInvoice(number)
                    .then(result=>{
                              if (!result)
                                {reject('Error confirming the invoice');}
                    });
            });
        resolve(true);  // Resolving here doesn´t mean that all promises were completed!

    });
}

init()  // triggered at load..
{
    let invoices = [2,4,8,16,31];
    processInvoices(invoices)
        .then(result=>
            { 
                if (result) // It´s probable that the following message isn´t accurate:
                    console.log('All invoices were processed');
            }).catch(reason=>{console.error(reason)});

}

With the code above, I can´t be sure that the "console.log (or any routine)" will be executed right away after all promises were completed.

UPDATE Promise.all(iterable) solves the problem :

processInvoices(invoices)
{
  return new promise((resolve,reject)=>
    {
        var promisesArray = []; // store all promises here
        invoices.forEach(number=> 
            {
            
                promisesArray.push(confirmInvoice(number));
                    
            });
       Promise.all(promisesArray).then (results=>{
        // validate all results and reject if necessary...
        if (validateResults(results)) {
        // if all ok
            resolve('All invoices were processed successfully');
          }
        else {
          reject('Error processing one or more invoices'); // just for demo purposes
         }
        });                 

    });
}

init()  // triggered at load..
{
    let invoices = [2,4,8,16,31];
    processInvoices(invoices)
        .then(result=>
            {                   
            console.log(result);
            }).catch(reason=>{console.error(reason)});

}

Upvotes: 1

Views: 617

Answers (1)

CertainPerformance
CertainPerformance

Reputation: 370679

forEach runs synchronously. If you want to wait for all Promises to resolve before the full processInvoices resolves, you should use Promise.all instead; map each invoice number to a Promise and call Promise.all on the resulting array of Promises. Also, your

if (!result) {resolve(false);}

sounds like it's an attempt to handle an error, in case there is no result - in this case, you should reject the Promise instead of calling resolve. Ideally, the failed confirmInvoice call would result in a rejected Promise, but if that's not something you can fix, throw an error if result is falsey so you can handle it in a catch in init. For example:

function processInvoices(invoices) {
  return Promise.all(
    invoices.map(number => (
      confirmInvoice(number)
        .then(result => {
          // This will result in the `Promise.all` rejecting:
          if (!result) throw new Error('Failed to confirm invoice ' + number);
        })
    ))
  );
}
function init() {
  const invoices = [2, 4, 8, 16, 31];
  processInvoices(invoices)
    .then(result => {
        console.log('All invoices were processed');
    })
    .catch((err) => {
      console.log('err: ' + err);
    });
}

Upvotes: 1

Related Questions