DEV Community

Stefania Barabas
Stefania Barabas

Posted on

#JS📔: Mastering Async/Await in Javascript

Hi there,

I am back with a new article from the ♻️ Knowledge seeks community 🫶 collection.

🚀 Get articles like this delivered straight to your inbox. Subscribe to Stef’s Dev Notes.

In this article, we'll explore how async/await works, best practices for using it effectively, and common pitfalls to avoid.

Stef's Dev Notes - Async/await

Introduction

Asynchronous programming is an essential part of JavaScript, especially in modern web development where non-blocking operations like API calls and timers are common.

Over the years, JavaScript has evolved from callbacks to Promises, and now to async/await, which provides a more readable and maintainable way to handle asynchronous code.

What is Async/Await?

async/await is a syntactic improvement over Promises that makes asynchronous code look and behave more like synchronous code.

  • The async keyword is used to define a function that returns a Promise.
  • The await keyword pauses execution until the Promise is resolved, making it easier to work with asynchronous operations

Stef's Dev Notes - Async/await 2

This is a basic example where fetchData automatically returns a Promise. When we call it, we can use .then() to handle the resolved value.

But let’s see how await works in an example where we fetch data from an external API.

Stef's Dev Notes - Async/await 3

This time, we don’t use the .then() chains and instead we use await, which pauses the execution of the function until the API call completes.

🙋 Problems and solutions

Following the problem-solution approach, let’s tackle together some well known async/await problems and explore their optimal solutions. ⬇️

1. Performance Bottlenecks: Sequential vs. Parallel Execution

🔴 Problem: Fetching multiple API resources sequentially slows down the execution.

Stef's Dev Notes - Async/await 4

🟢 Solution: Use Promise.all() to fetch data in parallel.

Stef's Dev Notes - Async/await 5

Using Promise.all() will reduce overall response time by executing the requests concurrently.

2. Handling Multiple Requests Safely: Overloading the Server

🔴 Problem: Fetching too many resources at once (e.g., 100 API calls) can overwhelm the backend.

Stef's Dev Notes - Async/await 6

🟢 Solution: Use request throttling with p-limit.

What does throttling mean? Throttling is a technique used to limit the rate at which a function is called. Throttling transforms a function such that it can only be called once in a specific interval of time.

Stef's Dev Notes - Async/await 7

This prevents excessive concurrent requests, improving stability.

3. Dealing with Unresponsive APIs: Preventing Stuck Requests

🔴 Problem: API requests hang indefinitely if no response is received.

When making API calls, network requests can sometimes hang indefinitely due to server issues, bad internet connections, or backend timeouts. If you don’t set a timeout, your application might stay stuck waiting for a response that never arrives.

🟢 Solution: Implement timeouts with Promise.race().

JavaScript doesn’t have built-in timeout handling for fetch(), but Promise.race() allows us to race the API request against a timeout promise. The first promise that resolves or rejects will determine the outcome.

Stef's Dev Notes - Async/await 8

4. Handling Errors Properly: Avoiding Silent Failures

🔴 Problem: Unhandled async errors cause silent failures in production.

🟢 Solution: Use try/catch in order to handle async errors.

Stef's Dev Notes - Async/await 9

This makes error handling more intuitive and structured.

5. Debugging Async/Await Effectively

🔴 Problem: Tracing async function execution is challenging due to JavaScript’s event loop.

🟢 Solution: Use Chrome DevTools’ async call stack and structured logging.

In Chrome DevTools, go to the Sources tab, set breakpoints in async functions, and check the Call Stack section.

Enable async stack traces in Node.js using -async-stack-traces.

Use structured logging:

Stef's Dev Notes - Async/await 10
This makes debugging more efficient and traceable.

Conclusion

  • async/await makes asynchronous code more readable and maintainable.
  • Always handle errors with try/catch.
  • Use Promise.all() for parallel execution, but throttle when necessary.
  • Handle failures gracefully with Promise.allSettled() or timeouts with Promise.race().
  • Leverage DevTools and structured logging for better debugging.

By applying these solutions, you can write robust, scalable, and high-performance async JavaScript code. 🚀

Until next time 👋,

Stefania

💬 Let’s talk:

Have you encountered other async/await challenges?
Let’s discuss in the comments!


👋 Get in touch

You can find me on LinkedIn where I share updates about what’s next on Stef’s Dev Notes, as well as information about various tech events and other topics.

You can learn more about Frontend Development, JavaScript, Typescript, React, Next and Node.js in my newsletter: Stef’s Dev Notes.

Top comments (0)