Level up async understanding—Promises, async/await, and beyond.
Welcome to Asynchronous Mastery! Today we're diving into one of the most critical topics in Node.js development: understanding the difference between callbacks and promises. These concepts are essential for writing clean, efficient, and maintainable asynchronous code.
A callback is a function passed to another function as an argument, which is then called after the completion of some operation. While they are simple and effective, improper use can lead to callback hell - a nightmare of nested callbacks that makes code hard to read and maintain.
fs.readFile('file1.txt', function(err, data) {
if (err) throw err;
fs.readFile('file2.txt', function(err, data2) {
if (err) throw err;
console.log(data + data2);
});
});
const fs = require('fs').promises;
fs.readFile('file1.txt')
.then(data => fs.readFile('file2.txt'))
.then(data2 => console.log(data + data2))
.catch(err => console.error('Error:', err));
Welcome to the world of asynchronous programming with Node.js! In this chapter, we'll explore the async/await patterns that will help you write clean, efficient, and maintainable code. We'll cover everything from basic usage to advanced techniques like error handling, parallel execution, and cancellation patterns.
The async/await keywords are JavaScript's way of working with promises in a more readable and concise manner. They allow you to write asynchronous code that looks synchronous, making it easier to manage complex workflows.
async function fetchUser() {
try {
const response = await fetch('https://api.example.com/users');
const data = await response.json();
return data;
} catch (error) {
console.error('Error:', error);
}
}
When working with async/await, always wrap your asynchronous operations in a try/catch block to handle potential errors. This ensures that your application doesn't crash and provides meaningful error messages to the user.
async function example() {
try {
// Asynchronous code here
const result = await someAsyncFunction();
console.log(result);
} catch (error) {
console.error('An error occurred:', error);
}
}
Use Promise.all() to execute multiple asynchronous operations in parallel. This is particularly useful when you need to make several independent API calls or perform multiple database queries at the same time.
async function fetchMultipleResources() {
const [users, posts] = await Promise.all([
fetch('https://api.example.com/users'),
fetch('https://api.example.com/posts')
]);
const userData = await users.json();
const postData = await posts.json();
return { userData, postData };
}
Understand the difference between sequential execution (running tasks one after another) and parallel execution (running tasks simultaneously). Use parallel execution when you want to save time by running operations concurrently, but be mindful of resource constraints.
The Promise.race() method takes an array of promises and returns a new promise that resolves or rejects as soon as one of the promises in the array does. This is useful for implementing timeouts or selecting the fastest response among multiple services.
async function fetchWithTimeout(url, timeout) {
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {
reject(new Error('Request timed out'));
}, timeout);
});
try {
const response = await Promise.race([
fetch(url),
timeoutPromise
]);
return await response.json();
} catch (error) {
console.error(error);
}
}
One of the challenges with async/await is handling cancellations. When you need to cancel an ongoing asynchronous operation, you can use techniques like abortable promises or external cancellation tokens.
const controller = new AbortController();
async function fetchWithCancel(url) {
try {
const response = await fetch(url, { signal: controller.signal });
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
console.log('Request was canceled');
}
}
}
// To cancel the request:
controller.abort();
In this chapter, we've explored the core concepts of async/await in Node.js. You should now be comfortable with using these patterns to handle asynchronous operations, including error handling, parallel execution, and cancellation strategies. Remember to always write clean, readable code that follows industry best practices.
When working with asynchronous JavaScript, handling errors properly is crucial for building robust applications. This section explores how to manage thrown, rejected, and uncaught errors using modern practices.
throw
.reject()
or due to unhandled errors in the .then()
chain.Use try/catch
blocks for synchronous operations and async/await
with Promise.catch()
for asynchronous operations.
async function processData() {
try {
const result = await fetch('data.json');
return await result.json();
} catch (error) {
console.error('Error:', error);
throw new Error('Failed to process data');
}
}
Use .catch()
methods or combine with await/async
for cleaner error handling.
function getData() {
return fetch('data.json')
.then(response => response.json())
.catch(error => {
console.error('Data fetching failed:', error);
throw new Error('Data not available');
});
}
Always provide error handlers for asynchronous operations. Never leave promises or async functions unhandled.
// Bad practice - no error handling
fetch('data.json').then(data => processData(data));
// Good practice with catch
fetch('data.json')
.then(data => processData(data))
.catch(error => console.error('Error:', error));
try/catch
for cleaner code.Here's a complete example of error handling in a Node.js application using async/await and promises.
async function processPayment(cardData) {
try {
const paymentResult = await makePayment(cardData);
if (paymentResult.status === 'success') {
await sendConfirmationEmail(cardData.email);
return { success: true, message: 'Payment processed successfully' };
}
throw new Error('Payment failed');
} catch (error) {
console.error('[Payment Error]', error);
await notifySupport(error);
throw new Error('Payment processing failed. Please try again.');
}
}
processPayment({
cardNumber: '1234-5678-9012-3456',
email: 'user@example.com'
}).catch(error => {
console.error('Payment failed:', error);
});
Question 1 of 14