This error occurs when an asynchronous operation exceeds its configured time limit without completing. Common in network requests, database queries, and long-running async operations.
A TimeoutError is thrown when an async operation takes longer than the specified timeout duration. This is typically a deliberate safeguard to prevent operations from hanging indefinitely and consuming resources. In Node.js, timeouts can be implemented using Promise.race(), AbortController with AbortSignal.timeout(), or third-party libraries like Bluebird. The error indicates that while the operation may still be running, the caller has decided to stop waiting for it. Unlike connection errors or permission issues, a timeout doesn't necessarily mean the operation failed—it means it didn't complete within the expected timeframe. The underlying operation may still succeed later, but the result will be discarded.
Review your code to find where the timeout is set. Common locations include:
// HTTP client timeout
const response = await fetch('https://api.example.com', {
signal: AbortSignal.timeout(5000) // 5 second timeout
});
// Promise timeout with Promise.race()
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error('TimeoutError: Operation timed out')), 5000)
);
const result = await Promise.race([operation(), timeout]);
// Database client timeout
const result = await db.query('SELECT * FROM users', {
timeout: 5000
});Check configuration files, HTTP client settings, and database connection options.
Add logging to measure actual operation duration:
const startTime = Date.now();
try {
const result = await someAsyncOperation();
console.log(`Operation completed in ${Date.now() - startTime}ms`);
} catch (error) {
console.log(`Operation failed after ${Date.now() - startTime}ms`);
console.error(error);
}This helps determine if the timeout is too aggressive or if the operation genuinely needs optimization.
If operations legitimately require more time, increase the timeout:
// Using AbortSignal.timeout() (Node.js 16+)
const response = await fetch('https://slow-api.example.com', {
signal: AbortSignal.timeout(30000) // Increase to 30 seconds
});
// Using Promise.race()
const timeout = (ms) => new Promise((_, reject) =>
setTimeout(() => reject(new Error(`Timeout after ${ms}ms`)), ms)
);
const result = await Promise.race([
longRunningOperation(),
timeout(30000) // 30 second timeout
]);Balance between allowing sufficient time and preventing indefinite hangs.
For operations that consistently timeout, investigate performance:
Database queries:
// Add indexes for frequently queried fields
// Before: slow full table scan
const users = await db.users.findMany({
where: { email: '[email protected]' }
});
// After: create index on email column
// CREATE INDEX idx_users_email ON users(email);Network requests:
// Use connection pooling
const agent = new https.Agent({
keepAlive: true,
maxSockets: 50
});
// Cache frequently accessed data
const cachedData = cache.get(key);
if (cachedData) return cachedData;
const freshData = await fetchFromAPI();
cache.set(key, freshData, 300); // 5 min TTLParallelize independent operations:
// Serial (slow)
const user = await fetchUser(id);
const posts = await fetchPosts(id);
const comments = await fetchComments(id);
// Parallel (fast)
const [user, posts, comments] = await Promise.all([
fetchUser(id),
fetchPosts(id),
fetchComments(id)
]);Handle timeout errors gracefully with retry logic and user feedback:
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(url, {
...options,
signal: AbortSignal.timeout(5000)
});
return response;
} catch (error) {
if (error.name === 'TimeoutError' && attempt < maxRetries) {
console.log(`Attempt ${attempt} timed out, retrying...`);
await new Promise(resolve => setTimeout(resolve, 1000 * attempt)); // Exponential backoff
continue;
}
throw error;
}
}
}
// Usage
try {
const data = await fetchWithRetry('https://api.example.com/data');
} catch (error) {
if (error.name === 'TimeoutError') {
console.error('Operation timed out after multiple retries');
// Show user-friendly message
}
}Modern Node.js (16+) supports AbortController for proper cancellation:
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
try {
const response = await fetch('https://api.example.com', {
signal: controller.signal
});
clearTimeout(timeoutId); // Clear if successful
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
throw new Error('TimeoutError: Operation timed out after 5000ms');
}
throw error;
}This approach properly cancels the underlying operation instead of just discarding the result.
Promise Continuation After Timeout:
When a timeout occurs using Promise.race(), the original promise continues executing in the background. This can lead to unexpected side effects or resource leaks. Use AbortController to truly cancel operations when timeouts occur.
Event Loop Blocking:
Timeouts rely on the event loop being free to execute callbacks. If synchronous operations block the event loop, timeout handlers may not fire on time. Use --trace-warnings to detect blocking operations.
Distributed Systems Considerations:
In microservices, set timeouts at multiple levels: client timeout < service timeout < load balancer timeout. This ensures proper error propagation without cascading failures.
Timeout Granularity:
Node.js timers have minimum granularity dependent on the system (typically 1-15ms). Very short timeouts may not fire precisely when expected. Use process.hrtime() or performance.now() for high-precision timing measurements.
Memory Leaks:
Always clear timeout handles when operations complete successfully to prevent memory leaks:
const timeoutId = setTimeout(...);
try {
await operation();
} finally {
clearTimeout(timeoutId); // Essential cleanup
}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