Introduction
Async and Await are JavaScript keywords introduced in ECMAScript 2017 (ES8) that enable writing asynchronous code in a more readable, synchronous-like, and manageable manner. They simplify handling operations that take time to complete, such as fetching data from an API.
Before we dive in, let’s first understand the concepts of synchronous and asynchronous programming in JavaScript. In synchronous programming, tasks are executed sequentially, one after the other, in the order they appear. Each task must complete before the next one begins. On the other hand, asynchronous programming allows tasks to run in the background, enabling JavaScript to continue executing other tasks without waiting for the previous ones to finish.
As we know, JavaScript is a single-threaded language, meaning it can only execute one task at a time. If that's the case, how does JavaScript handle asynchronous code? This is made possible by the Event Loop, a key mechanism that works alongside the JavaScript runtime environment. The event loop enables asynchronous operations to run without blocking the main thread, ensuring that JavaScript stays responsive. Without further delay, grab a cup of coffee and let’s jump right into today’s topic!
What is Async?
To better understand this concept, we’ll take a more practical approach. Before the introduction of async and await, Promises were handled using the "old way," which was introduced in ES6 (ECMAScript 2015). Let’s explore the example below.
The code above demonstrates the traditional syntax for handling Promises. The Promise
constructor is used to create a new Promise instance. It accepts a function (known as the executor function) as its argument, which includes two parameters: resolve
and reject
. This executor function contains the logic for the asynchronous operation. In this example, resolve
is called immediately, signaling that the Promise has successfully completed with a specific value. Once the Promise is resolved, the .then
method is triggered, executing its callback to log the result.
However, this syntax can be somewhat tricky to remember. The introduction of async/await
simplified the process of handling promises, making it much easier to read and understand. Let’s look at an example below.
To implement an async
function, we use the async
keyword, which tells JavaScript that this is not a regular function but an asynchronous one. The second example shows how the same can be done using an arrow function.
Another important concept to note is the await
keyword. Both async
and await
work together to simplify handling Promises. The await
keyword can only be used inside an asynchronous function—it cannot be used outside a function or within a regular function. It must always appear within a function marked as async. Now that we’ve covered the basics, let’s dive deeper into the concept!
How It Works Behind the Scenes
Many JavaScript developers use async/await
regularly in their code, but only a few truly understand how it functions under the hood. That’s where this tutorial comes in. Let’s explore an example to break it down.
In this example, we use the .then
method to better understand how Promises work compared to the async/await
approach. When the handlePromise()
function is called, the code executes line by line. When JavaScript encounters the .then
method, it registers the callback to the Microtask Queue and immediately moves to the next line, printing 'hello world'.
Once all synchronous tasks are completed, the JavaScript engine checks the Microtask Queue for pending tasks. After five seconds, the setTimeout
completes, and its callback is pushed back to the call stack. At this point, the Promise is resolved, and the registered callback runs, logging the result.
In short, the JavaScript engine doesn’t wait, it moves directly to the next line of code. Now, does the same behavior apply when using async/await
, or does it work differently? Let’s find out!
In the example above, when the handlePromise()
function is called, the first line 'the start' is printed. JavaScript then encounters the await
keyword, which indicates that the function is asynchronous and involves a Promise. It identifies that the Promise will take five seconds to resolve due to the setTimeout
. At this point, the handlePromise()
function is suspended (removed from the call stack), and any code after the await inside the function is paused.
The JavaScript engine continues to execute the rest of the program. After five seconds, the Promise is resolved, the suspended function is returned to the call stack, and the remaining lines inside handlePromise()
'Promise is resolved' and 'the end' are executed in sequence.
It’s important to note that suspending the function doesn’t block the main thread. If there’s other code written outside the handlePromise()
function, it will execute while the Promise is waiting to resolve.
The example below demonstrates this behavior in action:
In this example, the first output is the start
. When JavaScript encounters the await keyword, it recognizes that the Promise will take five seconds to resolve. At this point, the function is suspended, and JavaScript moves on to execute any code outside the function. As a result, We are outside
is printed next.
Once the Promise is resolved after five seconds, the handlePromise()
function is restored to the call stack, and its remaining lines are executed, printing Promise is resolved
followed by the end
.
let's examine one more example, we'll try making an API call with async/await
in other to understand the the concept better.
In the code above, the execution process follows the same principles discussed earlier. When JavaScript encounters the fetch
function, it suspends the getData()
functions and wait for the fetch call to return a response object, this object contains various properties such as the status, headers and body of the response. The function then resumes execution once the response is available.
The response body is the data we need, but it is in raw form (such as text or binary) and not immediately usable. To convert it into a JavaScript object for easier manipulation, we use the .json()
method, which parses the raw JSON response. This process involves another Promise, which is why the second await
is necessary. The function suspends again until the Promise resolves.
Once both Promises are fulfilled, the getData()
function resumes, and the parsed data is printed to the console. A straightforward way to explain how fetch works, isn’t it? Now, back to our main discussion!. What if our API response fails? How do we manage errors with async/await
? Let’s dive into this in the next section.
Handling Errors with Async/Await
Traditionally, errors in Promises were handled using the .catch
method. But how can we handle errors when using async/await? This is where the try...catch
block comes into play.
In the code above, the Promise is enclosed within a try
block, which executes if the Promise resolves successfully. However, if the Promise is rejected, the error is caught and handled within the catch
block.
But did you know we can still handle errors the traditional way? Here’s an example:
To handle errors in async/await using the traditional approach, you simply attach the catch
method to the function, as demonstrated above. It functions in the same way as the try/catch block.
Conclusion
Async/await has revolutionized how JavaScript handles asynchronous operations, making code more readable and easier to manage compared to traditional methods like .then
and .catch
. By leveraging async and await, we can write asynchronous code that feels more synchronous, improving overall code clarity. While understanding the inner workings—such as the event loop and microtask queue, implementing async/await is straightforward and highly effective for modern JavaScript development. With proper error handling using try/catch
or .catch
, we can confidently manage both successful and failed promises.
Thanks for sticking around! I hope this article made async/await a bit clearer for you. Wishing you success in your coding adventures—go build something amazing!
Top comments (0)