javascriptasynchronousasync-await

JavaScript await inside if statement delays code execution outside of if block


I have always thought that async/await is just a syntactical sugar around Promises and before actually executing it, JavaScript engine:

  1. Wraps code after await into .then()
  2. Wraps code inside catch {} block into .catch()
  3. Wraps code inside finally {} block into .finally()

But then on the MDN article about await I have read that await

pauses the execution of its surrounding

Which is a fundamentally different thing. I came up with an example of testing that.

const promise = new Promise((res, rej) => {
  setTimeout(() => {
    console.log('promise resolves');
    res();
  }, 2000);
});

async function asyncFunc() {
  if (Math.random() > 0.5) {
    await promise;
    console.log('#1 this executes after a delay 50% times');
  }
  else {
    console.log('#2 this executes immediately 50% times');
  }

  console.log('this should not wait for promise to resolve but it does');
}

asyncFunc();

The code above proofs the statement that code execution actually pauses, because if we try to translate it into the "promisified" version we will have a different code, namely the console.log statement that was outside of if/else statement should be duplicated in order to replicate the async/await behavior

const promise = new Promise((res, rej) => {
  setTimeout(() => {
    console.log('promise resolves');
    res();
  }, 2000);
});

function promiseFunc() {
  if (Math.random() > 0.5) {
    promise.then(() => {
      console.log('#1 this executes after a delay 50% times')
      console.log('this code, that was outside of if/else either goes here')
    })
  }
  else {
    console.log('#2 this executes immediately 50% times');
    console.log('or here, based on the result of if')
  }
}
promiseFunc();

This makes me feel that Promise and async/await are either slightly different things, or the code translation is extremely counterintuitive.

UPD. From my point of view, an intuitive translation of the async code above should look like this.

const promise = new Promise((res, rej) => {
  setTimeout(() => {
    console.log('promise resolves');
    res();
  }, 2000);
});

function promiseFunc() {
  if (Math.random() > 0.5) {
    promise.then(() => {
      console.log('#1 this executes after a delay 50% times')
    })
  }
  else {
    console.log('#2 this executes immediately 50% times');
  }

  console.log('this is outside of if/else block and should be executed regardless of await keyword above');
}
promiseFunc();

The question is:

  1. If it actually does a pause, can we tell that async/await and promises are not the same?
  2. If it actually translates to Promise version before execution, why it ignores code blocks and spreads beyond them?

Solution

  • If it actually does a pause, can we tell that async/await and promises are not the same?

    No, you can't really tell the difference. The translation indeed looks more like in your second code snippet, though of course the compiler doesn't care whether it duplicates the code (or actually just references the same code twice). Notice that even as a programmer you don't have to duplicate code for conditional promise flows.

    If it actually translates to Promise version before execution, why it ignores code blocks and spreads beyond them?

    It doesn't ignore all code blocks. It translates them.

    The full quote from MDN is actually "… pauses the execution of its surrounding async function". It doesn't pause the execution of the if block only.

    Yes, translating control structures from await to .then() code is not immediately intuitive - in particular when it comes to loops or try/catch. But that's the entire point of async/await syntax - you don't have to write these complex .then() chains any longer, and you don't need to translate anything or even think about translating it - you just use the ordinary control flow structures in asynchronous code that you're used to from synchronous code.

    Whether the compiler does a complicated translation under the hood, or uses a different and more efficient execution model, doesn't matter and is not really observable.