3

I thought I understood how the async/await works in Javascript until I ran the code:

let flag = false;

function test() {
  [1, 2, 3].map(async n => {
    console.log(`flag set at ${n} before if?`, flag);
    if (!flag) {
      const x = await a(n);
      // do something with x
      flag = x;
      console.log('flag set!');
    }
    console.log(`flag set at ${n} after if?`, flag);
  });
}

function a(n) {
  return new Promise((resolve, reject) => {
    setTimeout(function() {
      resolve(n);
    });
  });
}

test();

The actual output is:

flag set at 1 before if? false
flag set at 2 before if? false
flag set at 3 before if? false
flag set!
flag set at 1 after if? 1
flag set!
flag set at 2 after if? 2
flag set!
flag set at 3 after if? 3

Which is very different from what I thought it should be:

flag set at 1 before if? false
flag set!
flag set at 1 after if? 1
flag set at 2 before if? 1
flag set at 2 after if? 1
flag set at 3 before if? 1
flag set at 3 after if? 1

I think I need to be educated. Thanks.

Update: Thanks for the comments mentioning about the map. When I changed my code to be the following, it worked as expected:

let flag = false;

async function test() {
  for (const n of [1, 2, 3]) {
    console.log(`flag set at ${n} before if?`, flag);
    if (!flag) {
      const x = await a(n);
      // do something with x
      flag = x;
      console.log('flag set!');
    }
    console.log(`flag set at ${n} after if?`, flag);
  }
}

function a(n) {
  return new Promise((resolve, reject) => {
    setTimeout(function() {
      resolve(n);
    });
  });
}

test();
8
  • 5
    map does not await, it simply turns your original array into an array of promises. Commented Jul 30, 2019 at 20:41
  • 1
    For a sequential loop, use for of around the await. map just calls the callback function three times, and each of the three created promises is independent. Commented Jul 30, 2019 at 20:43
  • 1
    I am pretty sure it has to do with the use of map. If you were to use a for loop, I don't think you would have this output. Commented Jul 30, 2019 at 20:43
  • 2
    @Andrew Even though setTimeout calls the callback "immediately", it's not synchronous but some minimum value - and its on the macro task loop, so being delayed after all promise stuff for sure. Commented Jul 30, 2019 at 20:59
  • 2
    calling a setTimeout without a second argument is still going to be different than no setTimeout at all, as it will be put on the bottom of the browser async event queue ! Commented Jul 30, 2019 at 21:00

1 Answer 1

1

The await keyword make sure the current method thread wait for the function call to resolve before continuing.

Which is why every flag set! is printing before the after log.

However your execution is encapsulated in an asynchronous arrow function inside a .map. That means that, even if the arrow function itself is paused, the .map is not. As mentioned by @zerkms, it turns your array into an array of promises.

Think of it as an actual asynchronous call to a distant API. You aren't going to wait for your server to be done, all the calls from the .map will be done, and then evaluated as they come back for each initial value.

This is exactly what is happening here.

Sign up to request clarification or add additional context in comments.

3 Comments

I'm not saying that this answer is wrong, but some parts of it looks misleading, like "However your execution is encapsulated in the arrow function". Being an arrow function means adds to the problem.
Would you have a suggestion on how to formulate it better so it isn't misleading ? English isn't my first language so I might not always word things correctly :(
I reworded this passage using @zerkms explanation to hopefully clear things up.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.