Non-Blocking Asynchronous Code with Promises and Async/Await0%

Non-Blocking Asynchronous Code with Promises and Async/Await

Beginner12 min read•Updated: Jul 11, 2026
Study MaterialsCheatsheet

Non-Blocking Asynchronous Code with Promises and Async/Await

Asynchronous programming is the foundation of Node.js backend engineering. Over the years, JavaScript evolved from deeply nested "Callback Hell" to robust Promises and modern async / await syntax with structured error handling.


1. Evolution of Asynchronous Patterns

The Old Way: Callback Hell (Pyramid of Doom)

JavaScript
getUser(userId, (err, user) => {
if (err) return handleError(err);
getOrders(user.id, (err, orders) => {
if (err) return handleError(err);
getOrderDetails(orders[0].id, (err, details) => {
if (err) return handleError(err);
console.log('Order Details:', details);
});
});
});

The Modern Way: Async / Await with Clean Try / Catch

JavaScript
async function fetchUserDashboard(userId) {
try {
const user = await getUser(userId);
const orders = await getOrders(user.id);
const details = await getOrderDetails(orders[0].id);
return { user, recentOrder: details };
} catch (error) {
console.error('Failed to load dashboard:', error.message);
throw error;
}
}

2. Advanced Promise Combinators for Backend Performance

When building microservices and REST APIs, sequential await statements can cause unnecessary performance bottlenecks (waterfall requests). Node.js provides powerful Promise combinators:

1. Promise.all() (Fail-Fast Parallel Execution)

Executes promises in parallel. If all succeed, returns an array of results. If any reject, it rejects immediately.

JavaScript
const [userProfile, userCart, systemNotifications] = await Promise.all([
fetchUserProfile(userId),
fetchUserCart(userId),
fetchNotifications(userId)
]);

2. Promise.allSettled() (Resilient Aggregation)

Waits for all promises to finish, regardless of whether they resolve or reject. Ideal for batch jobs where one failure should not cancel others.

JavaScript
const results = await Promise.allSettled([
sendEmailNotification(user),
sendSmsAlert(user),
logAnalyticsEvent('LOGIN', user.id)
]);
 
results.forEach((res, index) => {
if (res.status === 'fulfilled') {
console.log(`Task ${index} succeeded:`, res.value);
} else {
console.error(`Task ${index} failed:`, res.reason);
}
});

3. Promise.race() vs Promise.any()

  • Promise.race([p1, p2]): Resolves or rejects as soon as the first promise settles. Perfect for request timeouts.
  • Promise.any([p1, p2]): Resolves as soon as the first promise succeeds. Only rejects if all promises reject.

Interactive Quiz

Multiple Choice Questions

Question 1
What problem does async/await primarily solve compared to deeply nested callback functions?
Question 2
When fetching three independent database queries simultaneously, why is await Promise.all([q1, q2, q3]) preferred over sequential await q1; await q2; await q3;?
Question 3
What is the key difference between Promise.all() and Promise.allSettled()?
Question 4
What will happen if an async function throws an unhandled error inside its body?
Question 5
How can a developer enforce a strict 3-second timeout on a slow external HTTP request using Promises?

Next Lesson

CommonJS vs ES Modules & Package.json Dependency Management

Continue learning with hands-on practice, examples, and exercises in the upcoming topic.

Practice Quiz

Test your understanding of this lesson with 5 questions. Each question has one correct answer.

PrevNext