본문으로 건너뛰기

timeout

Creates a Promise that rejects with a TimeoutError after a specified delay. Provides a clean way to implement timeout behavior in Promise-based operations. Perfect for creating timeout conditions in Promise.race scenarios, implementing deadline-based operations, and building timeout utilities. Leverages the delay function internally to ensure consistent timing and abort signal support.

Signature

const timeout: (ms?: number, options?: DelayOptions) => Promise<never>

Parameters

NameTypeDescription
ms-Timeout duration in milliseconds (default: 0)
options-Configuration options for timeout behavior

Returns

Promise that always rejects with TimeoutError after the specified delay

Examples

Basic timeout usage

import { timeout } from '@winglet/common-utils';

try {
// Will always throw TimeoutError after 5 seconds
await timeout(5000);
} catch (error) {
if (error instanceof TimeoutError) {
console.log('Timeout occurred:', error.message); // "Timeout after 5000ms"
console.log('Delay info:', error.extra.delay); // 5000
}
}

Creating timeout conditions with Promise.race

async function fetchWithTimeout(url: string, timeoutMs: number) {
try {
const result = await Promise.race([
fetch(url).then(r => r.json()),
timeout(timeoutMs) // This will throw if fetch takes too long
]);

return result;
} catch (error) {
if (error instanceof TimeoutError) {
throw new Error(`Request to ${url} timed out after ${timeoutMs}ms`);
}
throw error; // Re-throw fetch errors
}
}

// Usage
try {
const data = await fetchWithTimeout('/api/slow-endpoint', 3000);
console.log('Data received:', data);
} catch (error) {
console.error('Request failed:', error.message);
}

User interaction timeout

async function waitForUserAction(timeoutMs: number = 30000) {
return new Promise((resolve, reject) => {
let actionReceived = false;

// Setup user action handler
const handleAction = (event: Event) => {
if (!actionReceived) {
actionReceived = true;
document.removeEventListener('click', handleAction);
resolve(event);
}
};

document.addEventListener('click', handleAction);

// Setup timeout
timeout(timeoutMs).catch(error => {
if (!actionReceived) {
actionReceived = true;
document.removeEventListener('click', handleAction);
reject(error);
}
});
});
}

// Usage
try {
const clickEvent = await waitForUserAction(10000);
console.log('User clicked:', clickEvent);
} catch (error) {
if (error instanceof TimeoutError) {
console.log('User did not interact within 10 seconds');
}
}

Operation deadline enforcement

async function processWithDeadline<T>(
operation: () => Promise<T>,
deadlineMs: number
): Promise<T> {
const startTime = Date.now();

try {
return await Promise.race([
operation(),
timeout(deadlineMs)
]);
} catch (error) {
const elapsed = Date.now() - startTime;

if (error instanceof TimeoutError) {
throw new Error(
`Operation exceeded deadline of ${deadlineMs}ms (took ${elapsed}ms)`
);
}

throw error;
}
}

// Usage with file processing
try {
const result = await processWithDeadline(
() => processLargeFile('data.csv'),
60000 // 1 minute deadline
);
console.log('File processed successfully:', result);
} catch (error) {
console.error('Processing failed or exceeded deadline:', error.message);
}

Cancellable timeout with AbortSignal

async function cancellableTimeout() {
const controller = new AbortController();

// Cancel timeout if user performs action
document.addEventListener('keypress', () => {
console.log('User activity detected, cancelling timeout');
controller.abort();
}, { once: true });

try {
await timeout(5000, { signal: controller.signal });
console.log('This should not execute if user presses a key');
} catch (error) {
if (error instanceof AbortError) {
console.log('Timeout was cancelled:', error.message);
} else if (error instanceof TimeoutError) {
console.log('Timeout completed - no user activity detected');
}
}
}

Building a timeout utility class

class TimeoutManager {
private timeouts = new Map<string, AbortController>();

async createTimeout(id: string, ms: number): Promise<never> {
// Cancel existing timeout with same ID
this.cancel(id);

const controller = new AbortController();
this.timeouts.set(id, controller);

try {
return await timeout(ms, { signal: controller.signal });
} finally {
this.timeouts.delete(id);
}
}

cancel(id: string): boolean {
const controller = this.timeouts.get(id);
if (controller) {
controller.abort();
this.timeouts.delete(id);
return true;
}
return false;
}

cancelAll(): void {
for (const controller of this.timeouts.values()) {
controller.abort();
}
this.timeouts.clear();
}
}

// Usage
const timeoutManager = new TimeoutManager();

// Create named timeouts
timeout Manager.createTimeout('user-session', 1800000) // 30 min session
.catch(error => {
if (error instanceof TimeoutError) {
console.log('User session expired');
redirectToLogin();
}
});

// Cancel specific timeout
userActivity$.subscribe(() => {
timeoutManager.cancel('user-session');
// Restart session timeout
timeoutManager.createTimeout('user-session', 1800000);
});

Testing timeout scenarios

describe('Timeout behavior', () => {
it('should timeout after specified duration', async () => {
const start = Date.now();

await expect(timeout(100)).rejects.toThrow('Timeout after 100ms');

const elapsed = Date.now() - start;
expect(elapsed).toBeGreaterThanOrEqual(100);
expect(elapsed).toBeLessThan(150); // Allow some variance
});

it('should include delay information in error', async () => {
try {
await timeout(250);
fail('Should have thrown TimeoutError');
} catch (error) {
expect(error).toBeInstanceOf(TimeoutError);
expect(error.message).toBe('Timeout after 250ms');
expect(error.extra.delay).toBe(250);
}
});

it('should be cancellable with AbortSignal', async () => {
const controller = new AbortController();

setTimeout(() => controller.abort(), 50);

await expect(
timeout(100, { signal: controller.signal })
).rejects.toThrow('Abort signal received');
});
});

Playground

import { timeout } from '@winglet/common-utils';

try {
// Will always throw TimeoutError after 5 seconds
await timeout(5000);
} catch (error) {
if (error instanceof TimeoutError) {
  console.log('Timeout occurred:', error.message); // "Timeout after 5000ms"
  console.log('Delay info:', error.extra.delay);   // 5000
}
}

Notes

Key Features:

  • Guaranteed Rejection: Always throws TimeoutError after specified delay
  • Detailed Error Info: TimeoutError includes original delay value in extra data
  • Cancellation Support: Full AbortSignal integration via underlying delay function
  • Consistent Timing: Uses same delay mechanism as other promise utilities
  • Race Condition Safe: Designed for use with Promise.race scenarios

Error Types:

  • TimeoutError: Thrown when timeout duration elapses normally
  • AbortError: Thrown when cancelled via AbortSignal before timeout

Common Patterns:

  • Promise.race: Combined with async operations to enforce time limits
  • Deadline Enforcement: Set maximum execution time for operations
  • User Interaction: Wait for user actions with timeout fallback
  • Session Management: Implement automatic session expiration
  • Resource Cleanup: Trigger cleanup operations after delays

Best Practices:

  • Always handle TimeoutError specifically in catch blocks
  • Use meaningful timeout values based on expected operation duration
  • Consider user experience when setting timeout durations
  • Provide clear feedback when operations timeout
  • Use AbortSignal for cancellation in interactive scenarios

Performance Considerations:

  • Memory Usage: ~50-90 bytes per timeout (inherits from delay + error object)
  • CPU Overhead: <0.2ms for timeout setup and error creation
  • Timing Precision:
    • Browser: ±1-4ms accuracy (subject to background tab throttling)
    • Node.js: ±1ms accuracy (more reliable for precise timeouts)
    • Minimum timeout: 1ms (0ms timeouts fire immediately)
  • Environment Differences:
    • Browser: Throttled in background tabs (1000ms max), affected by system performance
    • Node.js: Consistent timing, not affected by UI thread blocking
    • Web Workers: Full precision maintained, not throttled
  • Error Object Size: TimeoutError ~200-300 bytes including stack trace
  • Cleanup Efficiency: Automatic via delay function's AbortSignal integration