5

Why does this snippet

const firstArray = ['toto', 'toto'];
const secondArray = ['titi', 'titi'];
firstArray.forEach(async (toto, i) =>
{
  await secondArray.forEach(async titi =>
  {
    // async code
    console.log(titi, i);
  });
  // async code
  console.log(toto, i);
});

produce the following output:

output

Removing the await keyword produces the expected output

enter image description here

My guess is it resides in the await keyword's behaviour, as, without which, the produced output is the expected output.

EDIT: this is a purely trivial question. I want to understand why using await before a forEach provides this behaviour. There is no 'concrete code' behind this.

EDIT2: edited the snippet as the comments and answer reflected misunderstanding of my question

mjarraya
  • 1,116
  • 1
  • 11
  • 18
  • I get `titi titi titi titi toto toto` from your snippet and the other version if I move the first await to the second `console.log`, I may be missing something though? – Josh Rumbut Apr 17 '18 at 09:31
  • 2
    But `await`-ing the returned value of `forEach` does not make sense anyway, as `await` is made to receive a `Promise`, so that your code looks synchronous while being asynchronous, but `forEach` returns `undefined`. This does not explain why your snippet behaves the way you say though. – sp00m Apr 17 '18 at 09:33
  • 1
    You should post all your code – Oscar Paz Apr 17 '18 at 09:35
  • @JoshRumbut We get the same output. Might edit the question to make it clearer. The first await isn't to be moved, it is to be removed – mjarraya Apr 17 '18 at 09:35
  • I was just playing around when I noticed this, it isn't actually needed either @sp00m – mjarraya Apr 17 '18 at 09:35
  • there is no promise that resolves later in time in your code so there is nothing to `await` – Endless Apr 17 '18 at 09:39
  • 2
    When using async/await it's better to stay in the same scope so a foo...of loop is better then forEach – Endless Apr 17 '18 at 09:40
  • I usually avoid doing this, hence my surprise @Endless – mjarraya Apr 17 '18 at 09:46
  • Possible duplicate of [Using async/await with a forEach loop](https://stackoverflow.com/questions/37576685/using-async-await-with-a-foreach-loop) – Suhail Gupta Apr 17 '18 at 10:22

4 Answers4

5

forEach works synchronously meaning it doesn't do anything with the return of each iteration (in this case, the promise) and it ignores it.

If you use instead:

for(let item of arrayItems)

or a normal for loop you should see the expected result working asynchronously.

askilondz
  • 3,264
  • 2
  • 28
  • 40
0

I guess you want to await all promises before continuing. If should be the case, a combination of Array.prototype.reduce(...), Array.prototype.map(...) and Promise.all(...) will better suit:

const allPromises = firstArray.reduce((accumulatedPromises, toto) => {
  const secondArrayPromises = secondArray.map((titi) => {
    return // some promise;
  });
  const firstArrayPromise = // some promise;
  return accumulatedPromises.concat(secondArrayPromises, firstArrayPromise);
}, []);
const finalPromise = Promise.all(allPromises);

Then, either await finalPromise if you're within an async function, or use .then(...).

sp00m
  • 47,968
  • 31
  • 142
  • 252
0

Your outer forEach loop spits out two functions (note the async type you have; async functions automatically return a Promise) directly on the stack that then runs in sequence before finally outputting from the firstArray. So after both the functions are executed your output for the outer loop is printed. This is how the forEach loop works with async functions.

To use async/await with loops, you would want to have:

async function boot() {
  for(let item of list) {
    await getData(); // Here getData returns a promise
  }
}

boot();

Another way could be to use bluebird for Promise:

let exec = [];
await Promise.map(list => getData());
// Here Promise.map iterates over list
// and returns a list of promises which are 
// then resolved using await
Suhail Gupta
  • 22,386
  • 64
  • 200
  • 328
-1

The key to understanding is that await is asynchronous. So everything after will be executed no earlier than the current execution thread returns. Thus, the part

// async code
console.log(toto);

is put to a queue rather than being executed immediately.

Async/await essentially wraps your code like this:

const firstArray = ['toto', 'toto'];
const secondArray = ['titi', 'titi'];
firstArray.forEach((toto,i) => new Promise((resolve)=>
{
  resolve((new Promise((resolve)=>resolve(secondArray.forEach(titi => new Promise((resolve)=>
  {
    resolve(console.log(titi, i));
  }))))).then(()=>console.log(toto,i))
);}));

Now consider this code as well (setTimeout is pretty much a promise that is resolved after a given amount of time):

const firstArray = ['toto', 'toto'];
const secondArray = ['titi', 'titi'];
firstArray.forEach((toto, i) =>
{
  secondArray.forEach(titi =>
  {
    console.log(titi, i);
  });
  setTimeout(()=>console.log(toto, i));
});
Andrew Svietlichnyy
  • 743
  • 1
  • 6
  • 13