Handling Fetch Timeouts with Node.js AbortController
Problem
When calling external APIs, requests can hang indefinitely if the server doesn’t respond. A slow or failing third-party API takes your service down with it. The setTimeout + Promise.race workaround results in messy code.
Solution
AbortController provides a clean way to add timeouts to fetch requests.
async function fetchWithTimeout(url, timeoutMs = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, { signal: controller.signal });
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
throw new Error(`Request timed out after ${timeoutMs}ms`);
}
throw error;
} finally {
clearTimeout(timeoutId);
}
}
// Usage
const data = await fetchWithTimeout('https://api.example.com/users', 3000);
Node.js 20+ offers an even simpler approach with AbortSignal.timeout():
// One line is all you need
const response = await fetch('https://api.example.com/users', {
signal: AbortSignal.timeout(3000),
});
Cancelling multiple requests at once is also straightforward:
async function fetchMultiple(urls) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
try {
const results = await Promise.all(
urls.map((url) =>
fetch(url, { signal: controller.signal }).then((r) => r.json())
)
);
return results;
} catch (error) {
controller.abort(); // Cancel all remaining requests if one fails
throw error;
} finally {
clearTimeout(timeout);
}
}
Key Points
AbortControllerworks with more than fetch — it’s supported byaddEventListener,ReadableStream, and moreAbortSignal.timeout()(Node.js 20+) requires no manual cleanup, eliminating memory leak concerns- A single controller can cancel multiple requests simultaneously, making it ideal for parallel request management