18

I'm trying to use async and await in a function that uses a forEach loop. Annoyingly I can't get it to work. What should happen is it takes an array of event docs, loops through them, adds in some extra data, then pushes them to the events array. This events array is then returned from the original function. Here is my code:

async function getEvents() {
  ...
  var events = []
  await addExtrasToDocsForUser(docs, currentUserId, events)
  return events

}


var addExtrasToDocsForUser = (docs, currentUserId, events) => {
    return docs.forEach(async (eventDoc) => {
        const event = await addExtrasToDocForUser(eventDoc, currentUserId)
        events.push(event)
    })
}

What actually happens is the getEvents() function returns events as an empty array before the forEach loop has completed. How do I fix this?

frederj
  • 1,483
  • 9
  • 20
Tometoyou
  • 7,792
  • 12
  • 62
  • 108

3 Answers3

11

Basically, this is what happens inside forEach:

Array.prototype.forEach = function (callback) {
  for (let index = 0; index < this.length; index++) {
    callback(this[index], index, this);
  }
};

Actually, the real implementation is the following, but the bottom line is that we are not waiting for the callback to be done, so using a function that returns a promise won't wait for the promise to resolve everytime.

Your code is not complete and verifiable so I can't be sure that the following works, but it probably should act as you'd expect:

const addExtrasToDocsForUser = async (docs, currentUserId, events) => {
  for (let i=0; i<docs.length; i++) {
    const event = await addExtrasToDocForUser(docs[i], currentUserId);
    events.push(event);
  }
  return;
}

You might also want to check this CodeBurst article on foreach + async/await

Nino Filiu
  • 16,660
  • 11
  • 54
  • 84
  • In my `getEvents()` function, do I need to say `await addExtrasToDocsForUser(docs, currentUserId, events)` or can I remove the `await` there? – Tometoyou Feb 05 '19 at 11:02
  • Keep the `await`. If you remove it, it'll just launch the promise but not wait for the promise to resolve first. – Nino Filiu Feb 05 '19 at 11:43
9

Use Promise.all and map get all inner promises and return a single one that awaits them all:

const addExtrasToDocsForUser = async (docs, currentUserId, events) => {
    return Promise.all(docs.map(async (eventDoc) => {
        const event = await addExtrasToDocForUser(eventDoc, currentUserId)
        events.push(event)
    }));
}
jo_va
  • 13,504
  • 3
  • 23
  • 47
2

Why do you combine synchronous and asynchronous functions?
You call await addExtrasToDocsForUser(docs, currentUserId, events), but your function addExtrasToDocsForUser is not async.

var addExtrasToDocsForUser = async (docs, currentUserId, events) => {
  return await docs.forEach(async (eventDoc) => {
    const event = await addExtrasToDocForUser(eventDoc, currentUserId)
    events.push(event)
  })
}

You want to do something like this:

var someOperation = async (op0, op1) => {
 return op0+':'+op1
}

var fnWithForeach = async (docs, number, outputs)=>{
  return await docs.forEach(async (doc)=>{
 const output = await someOperation(doc, number)
 outputs.push(output) 
  })
}

async function getOutputs() {
  // ...
  var docs = ['A', 'B', 'C']
  var number = 10
  // ...
  var outputs = []
  await fnWithForeach(docs, number, outputs)
  return outputs
}

async function main() {
  // ...
  var outputs = await getOutputs()
  console.log(outputs)
  // ...
}

main()