What is async & await? How do they work?

By Prajwal Haniya

Tech-letter #8 | January 31,2023

What is async & await in JavaScript? How do they work under the hood?

JavaScript is a single-threaded language. A single-threaded language can execute only one task at a time that is it can only use one thread of execution.

What do you mean by a ‘thread of execution’?

A simple definition can be

A thread of execution is a single sequence of instructions that can be executed by a processor.

The immediate next question that can come to your mind is how the instruction to a processor is given and what steps are being followed? This can be slightly a hardware-level question which you can understand by reading this wonderful article.

As JavaScript is a single-threaded language, it becomes really difficult to build applications where you need to perform multiple tasks at the same given time. So, we have concepts like promises, async & await (uses promise), etc concepts which make JavaScript one of the most powerful languages to build scalable software applications.

JavaScript uses event loop and web APIs to perform tasks in an asynchronous and non-blocking manner. So what do you mean by event loop?

Understanding JavaScript’s event loop

The JavaScript engine has never done anything more than execute a single chunk of your program at any given moment. In JavaScript, the event loop is implemented by the JavaScript runtime, such as the browser’s JavaScript engine or Node.js, and it is responsible for managing the execution of code, including the execution of callbacks, and handling the scheduling of tasks. It allows the JavaScript engine to continue executing code while also waiting for other events to occur, such as user input, network requests, or timers.

what is a call stack?

According to the MDN docs

A call stack is a mechanism for an interpreter to keep track of its place in a script that calls multiple functions, what function is currently being run and what functions are called from within that function, etc.

Basically, a call stack keeps track of the function calls that have been made in the program. It acts like a memory by memorizing which function is running right now.

It is a LIFO(Last In First Out) data structure. And this is implemented in the JavaScript’s Engine. The call stack is used to manage the execution context and to ensure that function calls are properly nested and executed in the correct order. If the call stack becomes too large due to excessive function call or recursion, it can result in a “stack overflow” error, which can crash the program that is it can crash the page, which may lead to the page is unresponsive.

What the heck is execution context?

The JavaScript engine uses execution contexts to manage the execution of a program. When a program starts, an initial execution context is created. This is known as the global execution context and contains information about the global scope. The types of execution context are Global Execution Context & Function Execution Context.

Imagine the Execution context as a block with two divisions.

  1. Memory: where all variables and functions are declared.
  2. Thread of execution: where the code is executed line by line.

When the script first starts to run, it creates a global execution context & it represents the global scope in JavaScript. Whenever you call a function, the function execution context is created.

There are two phases in the creation of an execution context

These two phases happen every time a new execution context is created, whether it’s the global execution context, or a function execution context.

Run your code → Program starts → Creates an execution context (global) → calls a function → creates execution context (function) → for every function call the call stack keeps the track of it → after the job of the function → call stack pops it off

The above flow changes when the function call is asynchronous.

Understanding the global object

The global object acts as a container to all the global variables and functions.

For a web-browser window object is the global object. The window object provides access to the browser’s dom & other browsers-specific features. For Node.js global object is the global object, which provides access to the built-in modules and global variables. ******

Understanding async & await

The async & await is fairly easier concept to understand. But, it may sometimes feel difficult to understand what’s really happening under the hood when you write it in a large function.

The async keyword is used to declare an asynchronous function that returns a promise whereas await is a keyword used inside of an asynchronous function to wait for a promise to resolve before moving on to the next line of code. The await keyword pauses the execution of the function until the promise is resolved.

Here is an example of using async & await

// making an API call and handling the response
async function getDataThroughApi() {
  try {
    const response = await fetch("https://api.example.com/data");
    const data = await response.json();
		// Make use of the received data
    console.log(data);
  } catch (err) {
    console.error(err);
  }
}

getDataThroughApi();

With async and await you write asynchronous code which looks and is able to read like synchronous code. This reduces a lot of code that otherwise you have to chain along with the then and catch block.

Articles

  1. ECMAScript Specification related to the execution context
  2. Event loop and the rise of Async programming
  3. JavaScript: Call Stack explained
  4. Understanding Execution Context & Execution Stack in JavaScript
  5. Working of JavaScript under the hood