JavaScript await Tutorial

In this section we will learn what the await keyword is and how to use it in JavaScript.

Note: we’re assuming you already read the async function section and familiar with this topic.

What is await keyword in JavaScript

The await keyword is used to create an asynchronous operation in JavaScript.

The `await` operator can be used only inside the body of an `async function`.

JavaScript await syntax:

await value;

The value we put on the right side of this operator could be either a promise or a non-promise object.

JavaScript await operator with a Non-Promise object:

If the value we put on the right side of the await keyword is a non-promise object, it will automatically create a Promise object that is settled into resolve and put that value as the value of the Promise object.

After that, the execution engine puts the settled Promise in the microtask queue and jumps out of the async function. It basically moves back to the caller of the `async function` and runs the rest of the instructions after this call (if any).

At the end, when there was no other instruction in the Stack, the engine will invoke the task in the microtask queue and continue to execute the instructions in the asyn function right where the pause occurred.

Note: the `await` keyword will return the value of the settled Promise and not the promise itself.

Example: async function with await statement in JavaScript

console.log("Start");
async function func(){
  const res = await "Hello"; 
  console.log(`Inside the body of the async function 'func' with the message: ${res}`);
}
const prom= func();
console.log(prom);
prom.then(resolve=>{
  console.log("In the resolve handler");
  console.log(resolve);
}).catch(reject=>{
  console.log("In the reject handler");
  console.log(reject);
});
console.log("End");

Output:

Start

Promise {<pending>}

End

Inside the body of the async function 'func' with the message: Hello

In the resolve handler

undefined

How does await statement work in JavaScript?

Let’s start at the beginning of this program:

The execution engine first starts with the `console.log(“Start”);` statement. The message of this statement is simply sent to the browser’s console.

After that, the next statement for the engine to run is:

const prom= func();

Here, because there’s a call to the `func()` function, the engine will invoke the body of this function and starts to execute its instructions.

So let’s see what we have in this function:

The first statement is:

const res = await "Hello";

Here the value of the `await` operator is a non-Promise object. So the engine will create a resolved promise for this value and a task for it in the microtask queue.

At this time, instead of moving to the next instruction in the body of this function, the engine will move back to where the call to this function occurred and continue to run the next statement.

Note: The important note to take is that, because this function is not finished yet, the engine will return a pending Promise back to the caller of the function.

So in the statement:

const prom= func();

The `prom` identifer will have a pending Promise assigned to it.

That’s why when in the next statement we put this identifier as the argument to the `console.log()` The console printed a Promise with the pending state.

After that, we have the handlers of the `prom` Promise. But because this promise is not resolved yet, the engine will jump over the handler and moves to the instructions afterwards.

So the last statement to run is:

console.log("End");

So the message `End` appears on the browser’s console.

Now there’s nothing in the Stack to be run and so the engine will invoke the task in the microtask queue.

Here, because this task belongs to the `async function func`, it will back to the body of the `func()` function where the pause occurred.

There, the actual value of the resolved Promise returns and gets stored to the `res` identifier.

After that, the engine will move to the next statement in this function and run that as well.

The last statement is a call to the `console.log`, so its message will appear on the console.

At this time, the work of the async function `func()` is done and so the returned Promise of this function is also settled into resolve.

So now the resolve handler of the `prom` Promise object can be executed as well.

Note: because we didn’t specifically return any value from this function (via the `return` statement), in the resolved handler, the value of the `resolve` parameter becomes undefined.

JavaScript await operator with a Promise object:

If the value is a Promise object, as long as the promise object is not settled (either resolved or rejected), the execution of the rest of instructions in the body of that async function will be paused. In situations like this, the engine will return to the caller of the async function and continue to run the rest of instructions in the Stack.

After that all the tasks in the Stack are executed and the target Promise object (the one that we set as the value of the `await` operator) is settled (either resolved or rejected), a task with the value of the settled promise will be registered in the microtask queue to be invoked at the next round when the engine reached to this queue.

Finally, the engine will invoke the task and take out the value (AKA reason) and return it as the result of the `await` operator.

After that, it moves to the next instruction in the target `asyn function` and runs those instructions as well until there’s no other instruction to run.

At the end, the engine will return a resolved Promise from this async function.

Note: there’s a chance that the function might return a `rejected` Promise as well. We will run an example for this particular case and you’ll see how to handle it.

Alright, now let’s run an example and see this in practice:

Example: JavaScript await statement with a promise object

console.log("Start");
async function foo() {
  console.log(2);
  console.log(await new Promise((resolve,reject)=>{
    setTimeout(()=>resolve(8),0);
  }));
  console.log(9);
}
async function bar() {
  console.log(4);
  console.log(await 6);
  console.log(7);
}
console.log(1);
foo();
console.log(3);
bar();
console.log(5);
console.log("End");

Output:

Start

1

2

3

4

5

End

6

7

8

9

The runtime executes this example as follows:

  1. Print the message `Start`.
  2. Print the value 1.
  3. Invoke the async function `foo`.
  4. (inside foo) Print 2.
  5. (inside foo) Create a new Promise object and inside the executor function of this object call the `setTimeout` function to immediately put a request in the Task queue (AKA Message queue) for the execution of the target function that we set as the argument to the `setTimeout`.
    Note: At this time the API behind the `setTimeout` will send this request to the Task queue.
  6. Exit from the foo() function (This is because the `await` operator paused the execution of the foo function).
  7. Print 3.
  8. Invoke async function `bar`.
  9. (inside bar) Print 4.
  10. (inside bar) await keyword pauses the execution and adds a task on microtask queue for the immediately available value of 6.
  11. Leaving the `bar` function.
  12. Print 5.
  13. Print the message `End`.
  14. At this time all the instructions in the stack are executed.
  15. The engine now moves to the microtask queue to see if there’s a task to be run.
    Note: microtask queue will always runs before the Task queue (the Task queue is also called Message queue and this is where APIs like the `setTimeout` put their messages for the engine to run).
  16. There’s a task here in the microtask queue that belongs to the `bar` function and so the engine will move back to the `bar` function, where the execution paused.
  17. The engine will return the value `6` as the result of the `await` operation.
  18. (inside bar) Print 6.
  19. (inside bar) Print 7.
  20. bar return with a resolved Promise.
    Note: because we didn’t provide a handler for this Promise, it will be simply ignored.
  21. The engine checks the microtask again to see if there’s another task to be run.
  22. There’s nothing, so the engine moves to the `Task queue` (or the message queue).
  23. Here, the API behind the `setTimeout` function sent a request for a function to be invoked. So in the Task-Queue we have already a request waiting to be executed.
  24. The engine will invoke that function.
    Note: Just to remind you, the function is: ()=>resolve(8)
  25. Inside the body of this function there’s a call to the `resolve`.
  26. At this time, the Promise of the `await` operator in the `foo` function is settled to resolve and a task with the value of this resolved Promise will be registered in the microtask queue.
  27. Now the stack is empty again, so the execution engine moves to the microtask queue to see if there’s a task to be invoked.
  28. Of course we already registered one and so it will be invoked.
  29. At this stage, the engine will move back to the `foo` function where the execution paused.
  30. The result of the `await` operation will be returned and set as the argument of the `console.log()` statement.
  31. Print 8.
  32. Print 9
  33. Return a resolved Promise from the `foo()` function.

End

The order of the execution at first might seem a little confusing, but it just takes running a few more examples to get your head around it.

JavaScript await keyword with a Promise object that settles to rejection

Alright, if the value of the `await` operator is a Promise that settled to rejection, this essentially means error! So handling this problem:

  • We can put the `await` statement in a `try/catch` statement and handle the returned error this way.
  • If we don’t handle the error via a `try/catch` block, the function will return a rejected promise. So we can use a `catch` method to handle this rejected Promise.
  • At the end, if we don’t handle the error in any ways, it will cause the program to crash.

Example: JavaScript await keyword with rejected Promise Object

      console.log("Start");
      async function func(){
        try{
          await Promise.reject("Rejected");
        }catch(error){
          console.log("Inside the body of the catch block");
          console.log(error);
        }
      }
      func().then(resolve=>{
        console.log("Inside the body of the resolve handler");
        console.log(resolve);
      });
      console.log("End");
    </script>
   </body>
</html>

Output:

Start

End

Inside the body of the catch block

Rejected

Inside the body of the resolve handler

undefined

As you can see, the result of the `await Promise.reject();` statement is a promise that is immediately settled into rejection. This means error. But because we handled the error in the async function itself, after the catch block, the engine moves to run the rest of instructions in this function and at the end, the final result of this function is a resolved Promise.

Note: As mentioned before, an async function by default returns a resolved Promise object.

Example: await statement with a Promise that is rejected

Now let’s refactor the example above, but this time we don’t use the `try/catch` block.

console.log("Start");
async function func(){

  await Promise.reject("Rejected");
  console.log("This message will never be printed");
}
func().then(resolve=>{
  console.log("Inside the body of the resolve handler");
  console.log(resolve);
}).catch(reject=>{
  console.log("Inside the body of the rejection handler");
  console.log(reject);
});
console.log("End");

Output:

Start

End

Inside the body of the rejection handler

Rejected

Note: here after returning an error from the `await` operation, if there’re other instruction after this statement, they won’t be executed anymore. This is because the error is not handled properly.

So in this example the statement `console.log(“This message will never be printed”);` didn’t get executed because the result of the `await` statement is an error that is not handled in the async function itself.

Leave a Reply