Back to all posts

Writing Clean Asynchronous Code

S
sonhp
10 min read

Writing Clean Asynchronous Code

Asynchronous programming is essential in modern applications, but if not handled properly, it can lead to nested promises, callback hell, and unhandled exceptions.

In this guide, we’ll cover:

  • Why clean asynchronous code matters
  • Common async pitfalls
  • Using async/await properly
  • Handling concurrent operations
  • Error handling in async functions
  • Best practices for NestJS async services

1. Why Writing Clean Async Code Matters

When working with database queries, APIs, or background jobs, async operations can quickly become messy.

Bad async code leads to:
Deeply nested callbacks (callback hell)
Unreadable promise chains
Unhandled promise rejections
Race conditions in concurrent tasks

A well-structured async codebase is:
Easier to debug
More readable & maintainable
More performant


2. Common Mistakes in Asynchronous Code

Callback Hell

getUser(id, (user) => {
  getOrders(user.id, (orders) => {
    processOrders(orders, (result) => {
      console.log('Done:', result);
    });
  });
});

🚨 Problem: Deeply nested callbacks make code unreadable.

Solution: Use async/await.


Chaining Promises Poorly

this.userService
  .findUser(id)
  .then((user) => {
    return this.orderService.getOrders(user.id);
  })
  .then((orders) => {
    return this.paymentService.processPayments(orders);
  })
  .catch((error) => {
    console.error(error);
  });

🚨 Problem: Hard to read and maintain.

Solution: Use async/await.


Forgetting to Handle Async Errors

async function getUserData(id: string) {
  const user = await this.userService.findUser(id);
  const orders = await this.orderService.getOrders(user.id);
  return orders;
}

🚨 Problem: If findUser fails, the function crashes without handling the error.

Solution: Use try/catch blocks.


3. Using Async/Await Properly

Refactored Code with Async/Await

async function getUserOrders(userId: string) {
  try {
    const user = await this.userService.findUser(userId);
    if (!user) throw new NotFoundException('User not found');

    const orders = await this.orderService.getOrders(user.id);
    return orders;
  } catch (error) {
    throw new InternalServerErrorException(error.message);
  }
}

Easier to read
Proper error handling
No nested promises


4. Handling Concurrent Async Operations

Sequential Requests (Slow)

async function getUserData(userId: string) {
  const user = await this.userService.findUser(userId);
  const orders = await this.orderService.getOrders(user.id);
  return { user, orders };
}

🚨 Problem: Requests execute one after another, slowing down performance.


Using Promise.all for Parallel Execution

async function getUserData(userId: string) {
  const [user, orders] = await Promise.all([
    this.userService.findUser(userId),
    this.orderService.getOrders(userId),
  ]);

  return { user, orders };
}

🚀 Faster execution! Both requests run at the same time.

Improves performance
Reduces API/database calls time


5. Handling Errors in Async Functions

Forgetting await in Try/Catch

async function fetchData() {
  try {
    return this.httpService.get('https://api.example.com/data');
  } catch (error) {
    console.error('Failed to fetch data');
  }
}

🚨 Problem: The function doesn’t await the request, so errors won’t be caught.

Fix: Await Inside Try/Catch

async function fetchData() {
  try {
    const response = await this.httpService.get('https://api.example.com/data');
    return response.data;
  } catch (error) {
    console.error('Failed to fetch data:', error);
    throw new InternalServerErrorException('Data fetch failed');
  }
}

Now, errors are properly handled.


6. Best Practices for Async Code in NestJS

Use async/await over callbacks and promise chains
Handle errors with try/catch, not silently fail
Use Promise.all for parallel tasks
Always await async functions to catch errors properly
Wrap async handlers in NestJS Exception Filters


7. Summary: Writing Clean Async Code

Key Takeaways

Avoid callback hell – use async/await
Write clean, readable async functions
Handle errors with try/catch
Optimize performance with Promise.all

S

Written by sonhp

Technical writer and developer passionate about web technologies.

Related Articles