Documentation Index
Fetch the complete documentation index at: https://mintlify.com/visible/cruel/llms.txt
Use this file to discover all available pages before exploring further.
Overview
The Timeout pattern ensures that operations complete within a specified time limit. If an operation exceeds the timeout, it’s canceled and a CruelTimeoutError is thrown. This prevents resources from being held indefinitely by slow or hanging operations.
When to Use
- External API calls that might hang
- Database queries that could run too long
- Any I/O operation without built-in timeouts
- Preventing resource exhaustion from slow operations
- Enforcing SLA requirements
API Reference
Function Signature
function withTimeout<T extends AnyFn>(fn: T, options: TimeoutOptions): T
Options
Timeout duration in milliseconds. Must be at least 1. If the operation doesn’t complete within this time, it’s canceled.
Callback function executed when the timeout is triggered.
Examples
Basic Timeout
import { withTimeout } from 'cruel'
const fetchUser = async (id: string) => {
const response = await fetch(`https://api.example.com/users/${id}`)
return response.json()
}
const timedFetch = withTimeout(fetchUser, {
ms: 5000, // 5 second timeout
})
try {
const user = await timedFetch('123')
} catch (error) {
if (error instanceof CruelTimeoutError) {
console.log('Request timed out')
}
}
With Timeout Callback
import { withTimeout, CruelTimeoutError } from 'cruel'
const monitoredAPI = withTimeout(apiCall, {
ms: 3000,
onTimeout: () => {
console.warn('API call timed out after 3s')
metrics.increment('api.timeout')
},
})
Database Query Timeout
const executeQuery = async (sql: string) => {
const connection = await pool.getConnection()
try {
return await connection.query(sql)
} finally {
connection.release()
}
}
const timedQuery = withTimeout(executeQuery, {
ms: 10000, // 10 second timeout for queries
onTimeout: () => {
console.error('Database query timed out')
},
})
Multiple Timeout Tiers
// Fast operations
const fastAPI = withTimeout(getCachedData, { ms: 500 })
// Standard operations
const standardAPI = withTimeout(fetchData, { ms: 5000 })
// Slow operations
const slowAPI = withTimeout(generateReport, { ms: 30000 })
Per-Environment Timeouts
const timeouts = {
development: 30000, // Longer timeouts for debugging
staging: 10000,
production: 5000,
}
const apiCall = withTimeout(fetchData, {
ms: timeouts[process.env.NODE_ENV] || 5000,
})
Race with Timeout
// Manually racing with a timeout
async function fetchWithTimeout<T>(
promise: Promise<T>,
ms: number
): Promise<T> {
const timeoutPromise = new Promise<never>((_, reject) => {
setTimeout(() => reject(new CruelTimeoutError()), ms)
})
return Promise.race([promise, timeoutPromise])
}
// Better: use withTimeout
const timedFetch = withTimeout(fetch, { ms: 5000 })
Combining with Other Patterns
Timeout + Retry
import { withTimeout, withRetry } from 'cruel'
// Apply timeout first, then retry
const resilientAPI = withRetry(
withTimeout(apiCall, { ms: 5000 }),
{
attempts: 3,
delay: 1000,
backoff: 'exponential',
}
)
// Each retry attempt gets 5 seconds
Timeout + Fallback
import { withTimeout, withFallback } from 'cruel'
const safeAPI = withFallback(
withTimeout(fetchConfig, { ms: 3000 }),
{
fallback: defaultConfig,
onFallback: (error) => {
if (error instanceof CruelTimeoutError) {
console.log('Config fetch timed out, using defaults')
}
},
}
)
Timeout + Circuit Breaker
import { withTimeout, createCircuitBreaker } from 'cruel'
// Timeouts count as failures for circuit breaker
const resilientAPI = createCircuitBreaker(
withTimeout(apiCall, { ms: 5000 }),
{
threshold: 5,
timeout: 30000,
}
)
Timeout + Bulkhead
import { withTimeout, createBulkhead } from 'cruel'
// Prevent slow operations from holding bulkhead slots
const resilientAPI = createBulkhead(
withTimeout(apiCall, { ms: 5000 }),
{
maxConcurrent: 10,
maxQueue: 50,
}
)
With Compose
import { cruel } from 'cruel'
const resilientAPI = cruel.compose(apiCall, {
timeoutMs: 5000, // 5 second timeout
retry: {
attempts: 3,
backoff: 'exponential',
},
fallback: defaultValue,
})
Advanced Examples
Dynamic Timeout Based on Operation
function createAdaptiveTimeout<T extends AnyFn>(fn: T) {
return async (...args: Parameters<T>): Promise<ReturnType<T>> => {
const operationType = args[0]?.type
const timeoutMs = {
'quick': 1000,
'standard': 5000,
'batch': 30000,
}[operationType] || 5000
return withTimeout(fn, { ms: timeoutMs })(...args)
}
}
Timeout with Progress Tracking
function withProgressTimeout<T extends AnyFn>(
fn: T,
timeoutMs: number,
onProgress: (elapsed: number, total: number) => void
) {
return async (...args: Parameters<T>): Promise<ReturnType<T>> => {
const startTime = Date.now()
const progressInterval = setInterval(() => {
const elapsed = Date.now() - startTime
onProgress(elapsed, timeoutMs)
}, 1000)
try {
return await withTimeout(fn, { ms: timeoutMs })(...args)
} finally {
clearInterval(progressInterval)
}
}
}
const trackedAPI = withProgressTimeout(
apiCall,
10000,
(elapsed, total) => {
console.log(`Progress: ${elapsed}ms / ${total}ms`)
}
)
Cascading Timeouts
// Parent operation has longer timeout than children
const childOperation = withTimeout(fetchData, { ms: 3000 })
const parentOperation = withTimeout(
async () => {
const result1 = await childOperation('data1')
const result2 = await childOperation('data2')
return combine(result1, result2)
},
{ ms: 10000 } // Parent timeout is longer
)
Timeout with Cleanup
function withTimeoutCleanup<T extends AnyFn>(
fn: T,
timeoutMs: number,
cleanup: () => void
) {
return withTimeout(
async (...args: Parameters<T>) => {
try {
return await fn(...args)
} catch (error) {
cleanup()
throw error
}
},
{
ms: timeoutMs,
onTimeout: cleanup,
}
)
}
const apiWithCleanup = withTimeoutCleanup(
uploadFile,
30000,
() => {
console.log('Cleaning up partial upload')
cleanupTempFiles()
}
)
Error Handling
Timeout throws CruelTimeoutError:
import { CruelTimeoutError } from 'cruel'
try {
await timedAPI()
} catch (error) {
if (error instanceof CruelTimeoutError) {
console.log('Operation timed out')
// error.name === 'CruelTimeoutError'
// error.code === 'CRUEL_TIMEOUT'
// error.message === 'cruel: injected timeout'
}
}
Best Practices
- Set realistic timeouts: Too short and you’ll timeout valid operations, too long and you won’t prevent hangs
- Timeout external calls: Always timeout network requests and external API calls
- Layer timeouts: Parent operations should have longer timeouts than children
- Monitor timeout rates: High timeout rates indicate performance issues
- Combine with retries: Use retries to recover from transient timeout issues
- Use appropriate granularity: Database queries might need 10s, cache lookups 100ms
- Clean up on timeout: Release resources when operations timeout
- Log timeout events: Track which operations are timing out for debugging
Timeout Guidelines
| Operation Type | Recommended Timeout | Reasoning |
|---|
| Cache lookup | 100-500ms | Should be very fast |
| Database query | 5-10s | Depends on complexity |
| External API | 5-30s | Network latency + processing |
| File upload | 60-300s | Depends on file size |
| Report generation | 30-120s | CPU intensive |
| Health check | 1-3s | Must be fast |
| WebSocket message | 10-30s | Real-time expectations |
Common Patterns
API Client with Timeouts
class APIClient {
constructor(
private baseURL: string,
private defaultTimeout: number = 5000
) {}
async request(endpoint: string, options?: { timeout?: number }) {
const timeout = options?.timeout || this.defaultTimeout
const fetchFn = withTimeout(
async () => {
const response = await fetch(`${this.baseURL}${endpoint}`)
return response.json()
},
{ ms: timeout }
)
return fetchFn()
}
async quickRequest(endpoint: string) {
return this.request(endpoint, { timeout: 1000 })
}
async slowRequest(endpoint: string) {
return this.request(endpoint, { timeout: 30000 })
}
}
Service with Operation-Specific Timeouts
class UserService {
async getUser(id: string) {
// Fast read operation
return withTimeout(this.fetchUser, { ms: 3000 })(id)
}
async createUser(data: User) {
// Slower write operation
return withTimeout(this.insertUser, { ms: 10000 })(data)
}
async generateUserReport(id: string) {
// Very slow operation
return withTimeout(this.buildReport, { ms: 60000 })(id)
}
private async fetchUser(id: string) { /* ... */ }
private async insertUser(data: User) { /* ... */ }
private async buildReport(id: string) { /* ... */ }
}