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.
- Memory: where all variables and functions are declared.
- 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.
-
Global Execution Context
The Global Execution Context represents the global scope of the program and contains information about the global variables and functions that are available to all other execution contexts. When a program starts, the JavaScript engine creates the global execution context and pushes it onto the top of the
execution context stack
. This is the first execution context that is created and it represents the global scope of the program.The global execution context is also responsible for setting up the global object(
window object
in the browser &global object
in node.js), which is a built-in object that provides access to the global scope. -
Function Execution Context
The Function Execution Context represents the scope of the function and contains information about the variables and functions that are available within that scope. This new execution context contains information about the scope of the function, the arguments that were passed to the function, and the code that is about to be executed.
When the function call is completed the function execution context gets deleted.
There are two phases in the creation of an execution context
- Creation Phase
- Execution Phase
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
- ECMAScript Specification related to the execution context
- Event loop and the rise of Async programming
- JavaScript: Call Stack explained
- Understanding Execution Context & Execution Stack in JavaScript
- Working of JavaScript under the hood