ofetch includes built-in retry logic for failed requests, automatically retrying requests that fail with specific status codes.
Default Retry Behavior
By default, ofetch retries failed requests once for safe methods (GET, HEAD, OPTIONS, TRACE):
import { ofetch } from 'ofetch'
// Automatically retried once if it fails with a retryable status code
const data = await ofetch('/api/data')
Methods that may have side effects (POST, PUT, PATCH, DELETE) are not retried by default to avoid unintended duplicate operations.
Retries only occur when the response status code matches one of the retryable status codes. Network errors and timeouts are also retried.
Retry Status Codes
The following HTTP status codes trigger automatic retries:
- 408 - Request Timeout
- 409 - Conflict
- 425 - Too Early (Experimental)
- 429 - Too Many Requests
- 500 - Internal Server Error
- 502 - Bad Gateway
- 503 - Service Unavailable
- 504 - Gateway Timeout
These status codes typically indicate temporary issues that may succeed on retry.
Configuring Retry Count
You can customize the number of retry attempts using the retry option:
retry
number | false
default:"1 for safe methods, 0 for unsafe methods"
The number of times to retry the request. Set to false to disable retries completely.
import { ofetch } from 'ofetch'
// Retry up to 3 times
const data = await ofetch('/api/unreliable', {
retry: 3
})
// Disable retries
const data = await ofetch('/api/data', {
retry: false
})
// Enable retries for POST requests
const result = await ofetch('/api/create', {
method: 'POST',
body: { name: 'test' },
retry: 2
})
Retry Delay
You can add a delay between retry attempts:
retryDelay
number | ((context: FetchContext) => number)
default:"0"
Delay in milliseconds between retry attempts. Can be a fixed number or a function that returns the delay based on the fetch context.
Fixed Delay
import { ofetch } from 'ofetch'
// Wait 1 second between retries
const data = await ofetch('/api/data', {
retry: 3,
retryDelay: 1000
})
Dynamic Delay (Exponential Backoff)
import { ofetch } from 'ofetch'
// Exponential backoff: 500ms, 1000ms, 2000ms
const data = await ofetch('/api/data', {
retry: 3,
retryDelay: (context) => {
const retryCount = (context.options.retry as number) || 0
const maxRetries = 3
const attempt = maxRetries - retryCount
return Math.min(1000 * Math.pow(2, attempt), 10000)
}
})
Retry with Jitter
import { ofetch } from 'ofetch'
// Add random jitter to prevent thundering herd
const data = await ofetch('/api/data', {
retry: 3,
retryDelay: (context) => {
const baseDelay = 1000
const jitter = Math.random() * 500
return baseDelay + jitter
}
})
Custom Retry Status Codes
You can override the default retry status codes:
retryStatusCodes
number[]
default:"[408, 409, 425, 429, 500, 502, 503, 504]"
Array of HTTP status codes that should trigger a retry.
import { ofetch } from 'ofetch'
// Only retry on 429 (Too Many Requests) and 503 (Service Unavailable)
const data = await ofetch('/api/data', {
retry: 3,
retryStatusCodes: [429, 503]
})
// Retry on custom status codes
const data = await ofetch('/api/data', {
retry: 2,
retryStatusCodes: [408, 500, 502, 503, 504]
})
Retry with Rate Limiting
Handle rate limiting with exponential backoff:
import { ofetch } from 'ofetch'
const data = await ofetch('/api/rate-limited', {
retry: 5,
retryStatusCodes: [429], // Only retry on rate limit
retryDelay: (context) => {
// Check for Retry-After header
const retryAfter = context.response?.headers.get('Retry-After')
if (retryAfter) {
const delay = parseInt(retryAfter, 10)
return isNaN(delay) ? 1000 : delay * 1000
}
// Fallback to exponential backoff
const retryCount = (context.options.retry as number) || 0
const maxRetries = 5
const attempt = maxRetries - retryCount
return Math.min(1000 * Math.pow(2, attempt), 30000)
}
})
Abort Signal and Retries
Abort signals prevent retries from happening:
import { ofetch } from 'ofetch'
const controller = new AbortController()
// This will NOT retry if aborted
try {
const data = await ofetch('/api/data', {
retry: 3,
signal: controller.signal
})
} catch (error) {
console.error(error) // AbortError
}
// Abort the request
controller.abort()
When a request is aborted (via AbortController or timeout), ofetch will not retry the request, even if retry is configured.
Retry Logic Flow
- Request is made
- If request fails or returns a retryable status code:
- Check if
retry count is greater than 0
- Check if status code is in
retryStatusCodes (or default list)
- Check if request was not aborted
- If all checks pass:
- Wait for
retryDelay milliseconds
- Retry the request with
retry count decremented by 1
- If retry count reaches 0, throw
FetchError
Complete Example
import { ofetch } from 'ofetch'
async function fetchWithRetry() {
try {
const data = await ofetch('/api/unstable-endpoint', {
// Retry up to 3 times
retry: 3,
// Only retry on specific status codes
retryStatusCodes: [408, 429, 500, 502, 503, 504],
// Exponential backoff with max delay of 10 seconds
retryDelay: (context) => {
const retryCount = (context.options.retry as number) || 0
const maxRetries = 3
const attempt = maxRetries - retryCount
const delay = Math.min(1000 * Math.pow(2, attempt), 10000)
console.log(`Retry attempt ${attempt + 1}, waiting ${delay}ms`)
return delay
}
})
return data
} catch (error) {
console.error('All retry attempts failed:', error)
throw error
}
}