Built‑in timeout vs manual AbortController for phased axios request timeouts
0 reputation · 11 May 2026, 10:58 UTC
0 reputation · 11 May 2026, 10:58 UTC
I need to enforce separate connect and response timeouts for Axios requests while ensuring that any cleanup logic runs only once per request.
Axios provides a single timeout option that creates an internal AbortController and aborts the request when the limit is exceeded, but it does not expose that controller for custom error handling or phased timeouts. Using the modern signal option with a manually created AbortController lets me set separate timeouts via setTimeout, yet when both timeout and signal are present the internal timeout fires first and may leave the external signal’s abort listener to execute on an already‑aborted request, potentially causing duplicate cleanup. Moreover, the error objects differ (ECONNABORTED vs AbortError) and the abort reason is not automatically propagated.
timeout config and accept its limitations, or implement a manual AbortController with setTimeout to achieve phased timeouts?timeout and an external signal?Use a manual AbortController with separate setTimeout calls for connect and response phases. The built-in timeout option cannot distinguish between these phases, and combining both timeout and signal creates race conditions that risk duplicate cleanup execution.
Axios's timeout config implements a single deadline from request start. It uses an internal AbortController that aborts on one timer, making it impossible to enforce different limits for connection establishment versus response receipt. This design is intentional—Axios 1.x exposes the signal option precisely so you can replace this behavior with custom logic.
Implement phased timeouts by attaching your own AbortController and using request lifecycle hooks:
const controller = new AbortController();
const connectTimeout = setTimeout(() => {
controller.abort();
}, CONNECT_LIMIT);
const responseTimeout = setTimeout(() => {
controller.abort();
}, CONNECT_LIMIT + RESPONSE_LIMIT);
try {
const response = await axios.get(url, {
signal: controller.signal,
// other options
});
} catch (error) {
// Handle based on error.name === 'AbortError'
}
finally {
clearTimeout(connectTimeout);
clearTimeout(responseTimeout);
}
Set timeout: 0 to disable Axios's internal timeout entirely. This ensures only your manual abort logic executes:
await axios.get(url, {
signal: controller.signal,
timeout: 0 // Disable built-in timeout
});
When using manual abort, errors will be DOMException with name: 'AbortError'. Standardize your error handling:
if (error.name === 'AbortError') {
// Your cleanup logic runs once here
}
timeout: 5000 and external signal—observe duplicate cleanup logstimeout: 0 prevents internal abort from firingerror.code === 'ECONNABORTED' vs error.name === 'AbortError'Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.