How to handle asynchronous errors with try/catch in JavaScript
Learn to use async/await with try/catch blocks to manage network failures, timeouts, and promise rejections in modern JavaScript applications.
Modern JavaScript applications rely heavily on asynchronous operations like API calls and file reading. You must use try/catch blocks with async/await to intercept failures before they crash your application. These steps target Node.js 20.x or modern browsers running ES2022.
Prerequisites
- Node.js version 20.x or later installed on your system.
- A code editor like VS Code or Vim.
- Basic understanding of JavaScript Promises and the fetch API.
- A terminal or command prompt with npm access.
Step 1: Create a project structure
Initialize a new Node.js project to hold your test code. This creates a package.json file and a node_modules directory.
mkdir async-error-demo
cd async-error-demo
npm init -y
You will see a package.json file created with default settings. Create a new file named app.js in the current directory.
touch app.js
Step 2: Write the async function
Define an async function that attempts to fetch data from a public API. Use the fetch method to simulate a real-world network request. Include a try block to wrap the asynchronous operation.
const fetchData = async () => {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('Success:', data);
} catch (error) {
console.error('Fetch failed:', error.message);
}
};
fetchData();
This code attempts to fetch a specific todo item. If the HTTP status is not 2xx, it throws a custom error message. The catch block intercepts both the network error and the custom status error.
Step 3: Handle specific error types
Refine the error handling to distinguish between network errors and bad responses. Use a nested if statement inside the catch block to check the error type. This allows you to log different messages for timeouts versus server errors.
const fetchData = async () => {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
const data = await response.json();
console.log('Success:', data);
} catch (error) {
if (error.message.includes('fetch failed')) {
console.error('Network error:', error);
} else if (error.message.includes('HTTP error')) {
console.error('Server responded with error:', error.message);
} else {
console.error('Unexpected error:', error);
}
}
};
Check the error.message property to determine the specific failure reason. This logic ensures your application provides clear feedback to the user or developer.
Step 4: Handle multiple promises
Create a function that runs three concurrent requests. Wrap the Promise.all call in a try block to catch failures from any single request. If one request fails, Promise.all rejects immediately.
const fetchMultiple = async () => {
const promises = [
fetch('https://jsonplaceholder.typicode.com/todos/1').then(res => res.json()),
fetch('https://jsonplaceholder.typicode.com/todos/2').then(res => res.json()),
fetch('https://jsonplaceholder.typicode.com/todos/3').then(res => res.json())
];
try {
const results = await Promise.all(promises);
console.log('All results:', results);
} catch (error) {
console.error('One request failed:', error.message);
}
};
fetchMultiple();
This approach stops execution immediately if the first failed request occurs. The catch block logs the specific error from the failed promise. This prevents the application from continuing with incomplete data.
Step 5: Add timeout logic
Implement a timeout mechanism to prevent hanging requests. Use Promise.race to compare the fetch promise against a timeout promise. Throw an error if the timeout promise resolves first.
const fetchDataWithTimeout = async (url, timeout = 5000) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, { signal: controller.signal });
clearTimeout(id);
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
throw new Error('Request timed out');
}
throw error;
}
};
fetchDataWithTimeout('https://jsonplaceholder.typicode.com/todos/1')
.then(data => console.log('Data:', data))
.catch(err => console.error('Error:', err.message));
Set a 5-second limit on the request. If the server takes longer, the fetch call aborts and throws an AbortError. The catch block converts this into a user-friendly timeout message.
Verify the installation
Run the Node.js application to ensure the code executes without syntax errors. Navigate to your project directory in the terminal and start the script.
node app.js
You will see output indicating success or a specific error message. If the network is available, you will see the JSON data printed to the console. If the network is restricted, you will see the Fetch failed or Request timed out message.
Troubleshooting
If the code fails to run, check the Node.js version using the --version flag. Ensure you are using Node.js 18.x or higher for full async/await support. Verify that the fetch API is available in your environment. If using an older browser, you may need to polyfill the fetch API. Check the terminal output for stack traces that point to the exact line number of the error. Ensure you have not missed a closing brace in the try or catch block. If the error persists, reinstall Node.js from the official website to rule out corrupted binaries.