Skip to main content
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:
timeout
number
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)
}

Build docs developers (and LLMs) love