This error occurs when an asynchronous operation is cancelled using an AbortController before it completes. It commonly happens with fetch requests, file operations, or any API that accepts an AbortSignal when the operation is explicitly aborted or times out.
The AbortError is a DOMException that signals an operation was intentionally cancelled before completion. In Node.js, this error is thrown when code uses the AbortController API to stop ongoing asynchronous operations like network requests, file I/O, or stream processing. The AbortController interface provides a standardized mechanism for cancelling operations across different Node.js APIs. When controller.abort() is called, any operation using that controller's signal will stop and throw this error. This is not a bug but an expected behavior that requires proper handling. The error can be triggered programmatically by calling abort() or automatically through AbortSignal.timeout() when an operation exceeds a specified time limit. Understanding this pattern is essential for building responsive applications that can cleanly cancel unnecessary work.
Wrap operations that use AbortSignal in try-catch blocks and check for AbortError specifically:
const controller = new AbortController();
try {
const response = await fetch('https://api.example.com/data', {
signal: controller.signal
});
const data = await response.json();
console.log(data);
} catch (err) {
if (err.name === 'AbortError') {
console.log('Request was cancelled');
// Handle cancellation gracefully
} else {
console.error('Request failed:', err);
// Handle other errors
}
}This prevents unhandled promise rejections and allows you to differentiate between intentional cancellations and actual errors.
Before starting expensive operations, check if the signal is already aborted:
async function processData(signal) {
// Check before starting work
if (signal.aborted) {
console.log('Operation already cancelled');
return;
}
// Do some work
const data = await fetchData();
// Check again before continuing
if (signal.aborted) {
console.log('Operation cancelled during processing');
return;
}
// Continue processing
await saveData(data);
}This avoids wasting resources on operations that will be cancelled anyway.
Instead of manually managing timeouts, use the built-in timeout method:
try {
// Automatically abort after 5 seconds
const response = await fetch('https://api.example.com/slow', {
signal: AbortSignal.timeout(5000)
});
const data = await response.json();
} catch (err) {
if (err.name === 'TimeoutError') {
console.log('Request timed out after 5 seconds');
} else if (err.name === 'AbortError') {
console.log('Request was aborted');
} else {
console.error('Request failed:', err);
}
}TimeoutError is a specific type of AbortError that indicates a timeout occurred.
Listen for the abort event to perform cleanup when operations are cancelled:
const controller = new AbortController();
controller.signal.addEventListener('abort', () => {
console.log('Operation aborted, cleaning up...');
// Close database connections
// Clear intervals/timeouts
// Release resources
});
// Perform async operation
performLongRunningTask(controller.signal);
// Later, abort if needed
setTimeout(() => controller.abort(), 3000);This ensures resources are properly released when operations are cancelled.
Avoid creating new AbortController instances on every render:
import { useEffect, useRef } from 'react';
function DataComponent() {
const controllerRef = useRef(null);
useEffect(() => {
// Create controller once
controllerRef.current = new AbortController();
async function fetchData() {
try {
const response = await fetch('/api/data', {
signal: controllerRef.current.signal
});
const data = await response.json();
setData(data);
} catch (err) {
if (err.name !== 'AbortError') {
console.error('Fetch failed:', err);
}
}
}
fetchData();
// Cleanup: abort on unmount
return () => {
controllerRef.current?.abort();
};
}, []);
}This prevents memory leaks and ensures proper cancellation on component unmount.
Combining Multiple Abort Signals
Use AbortSignal.any() to create a signal that aborts when any of multiple signals abort:
const userController = new AbortController();
const timeoutSignal = AbortSignal.timeout(10000);
const combinedSignal = AbortSignal.any([
userController.signal,
timeoutSignal
]);
fetch('/api/data', { signal: combinedSignal });This is useful for implementing both user cancellation and automatic timeouts.
File System Operations with AbortSignal
Node.js fs module methods support AbortSignal for operations like reading files:
import { readFile } from 'fs/promises';
const controller = new AbortController();
try {
const data = await readFile('/path/to/large/file.txt', {
signal: controller.signal
});
} catch (err) {
if (err.name === 'AbortError') {
console.log('File read was cancelled');
}
}Express.js Request Abortion
In Express applications, handle client disconnections properly:
app.get('/api/long-task', async (req, res) => {
const controller = new AbortController();
req.on('close', () => {
if (!res.writableEnded) {
controller.abort();
res.status(499).end(); // Client Closed Request
}
});
try {
const result = await performTask(controller.signal);
res.json(result);
} catch (err) {
if (err.name !== 'AbortError') {
res.status(500).json({ error: err.message });
}
}
});Important Considerations
- AbortError should never be logged as an application error - it's expected behavior
- Always differentiate between AbortError and TimeoutError in error handling
- In production, avoid crashing the application on unhandled AbortError
- Use AbortSignal with Promise.race() for implementing request deduplication
- The abort signal pattern works with child processes, workers, and streams
Error: Listener already called (once event already fired)
EventEmitter listener already called with once()
Error: EACCES: permission denied, open '/root/file.txt'
EACCES: permission denied
Error: Invalid encoding specified (stream encoding not supported)
How to fix Invalid encoding error in Node.js readable streams
Error: EINVAL: invalid argument, open
EINVAL: invalid argument, open
TypeError: readableLength must be a positive integer (stream config)
TypeError: readableLength must be a positive integer in Node.js streams