ofetch provides built-in timeout support that automatically cancels requests that take too long to complete.
Basic Timeout
Set a timeout in milliseconds using the timeout option:
Request timeout in milliseconds. The request will be automatically aborted if it takes longer than this value.
import { ofetch } from 'ofetch'
// Timeout after 5 seconds
const data = await ofetch('/api/data', {
timeout: 5000
})
If the request exceeds the timeout, an error is thrown:
import { ofetch } from 'ofetch'
try {
const data = await ofetch('/api/slow-endpoint', {
timeout: 3000
})
} catch (error) {
console.error(error.cause.message)
// "The operation was aborted due to timeout"
console.error(error.cause.name)
// "TimeoutError"
console.error(error.cause.code)
// 23 (DOMException.TIMEOUT_ERR)
}
How Timeout Works
ofetch uses the modern AbortSignal.timeout() API to implement timeouts:
// Internally, ofetch does:
if (options.timeout) {
options.signal = AbortSignal.timeout(options.timeout)
}
When the timeout expires, the request is aborted with a TimeoutError.
Timeout functionality requires AbortSignal.timeout() support, which is available in Node.js 17.3+ and modern browsers.
Combining Timeout with AbortSignal
You can combine the timeout option with your own AbortSignal:
import { ofetch } from 'ofetch'
const controller = new AbortController()
// Request will abort on either timeout OR manual abort
const promise = ofetch('/api/data', {
timeout: 5000,
signal: controller.signal
})
// Manually abort before timeout
setTimeout(() => controller.abort(), 2000)
try {
await promise
} catch (error) {
console.error('Request aborted')
}
ofetch uses AbortSignal.any() to combine multiple abort signals:
// Internally, when both timeout and signal are provided:
options.signal = AbortSignal.any([
AbortSignal.timeout(options.timeout),
options.signal
])
The request will be aborted when either signal triggers.
Timeout with Retry
When using timeout with retry, each retry attempt gets its own timeout:
import { ofetch } from 'ofetch'
const data = await ofetch('/api/data', {
timeout: 3000, // 3 seconds per attempt
retry: 2, // Try up to 3 times total
retryDelay: 1000
})
In this example:
- First attempt: 3 second timeout
- Wait 1 second
- Second attempt: 3 second timeout
- Wait 1 second
- Third attempt: 3 second timeout
Total maximum time: (3 + 1 + 3 + 1 + 3) = 11 seconds
Timeout does NOT prevent retries. If a request times out and retry is configured, ofetch will retry the request. Set retry: false or retry: 0 to disable retries on timeout.
Disabling Retry on Timeout
If you want to timeout without retrying:
import { ofetch } from 'ofetch'
try {
const data = await ofetch('/api/data', {
timeout: 5000,
retry: false // Don't retry on timeout
})
} catch (error) {
console.error('Request timed out')
}
Different Timeouts for Different Requests
You can set different timeouts for different types of requests:
import { ofetch } from 'ofetch'
// Quick timeout for health checks
const health = await ofetch('/health', {
timeout: 1000
})
// Longer timeout for data fetching
const data = await ofetch('/api/large-dataset', {
timeout: 30000
})
// Very long timeout for file uploads
const upload = await ofetch('/api/upload', {
method: 'POST',
body: formData,
timeout: 60000
})
Global Timeout Configuration
Set a default timeout for all requests using $fetch.create():
import { $fetch } from 'ofetch'
const api = $fetch.create({
timeout: 10000 // 10 second default timeout
})
// Uses 10 second timeout
await api('/api/data')
// Override timeout for specific request
await api('/api/quick', { timeout: 2000 })
// Disable timeout for specific request
await api('/api/long-running', { timeout: undefined })
Timeout Error Handling
Distinguish between timeout errors and other errors:
import { ofetch, FetchError } from 'ofetch'
try {
await ofetch('/api/data', { timeout: 5000 })
} catch (error) {
if (error instanceof FetchError) {
if (error.cause?.name === 'TimeoutError') {
console.error('Request timed out after 5 seconds')
} else if (error.cause?.name === 'AbortError') {
console.error('Request was aborted')
} else {
console.error('Request failed:', error.message)
}
}
}
Timeout with Loading States
Combine timeout with loading states in UI:
import { ofetch } from 'ofetch'
let loading = true
let error = null
let data = null
try {
data = await ofetch('/api/data', {
timeout: 10000,
onRequest: () => {
loading = true
error = null
},
onResponse: () => {
loading = false
}
})
} catch (err) {
loading = false
if (err.cause?.name === 'TimeoutError') {
error = 'Request timed out. Please try again.'
} else {
error = 'Failed to load data'
}
}
Complete Example
import { ofetch } from 'ofetch'
async function fetchWithTimeout<T>(url: string, timeoutMs: number): Promise<T> {
const controller = new AbortController()
// Set up manual timeout
const timeoutId = setTimeout(() => {
controller.abort(new Error('Request timeout'))
}, timeoutMs)
try {
const data = await ofetch<T>(url, {
signal: controller.signal,
timeout: timeoutMs,
retry: 0, // Don't retry timeouts
onResponse: () => {
clearTimeout(timeoutId)
}
})
return data
} catch (error) {
clearTimeout(timeoutId)
if (error.cause?.name === 'TimeoutError' || error.cause?.name === 'AbortError') {
throw new Error(`Request to ${url} timed out after ${timeoutMs}ms`)
}
throw error
}
}
// Usage
try {
const data = await fetchWithTimeout('/api/data', 5000)
console.log(data)
} catch (error) {
console.error(error.message)
}