ofetch
semver
>=1.0.0postconditions6functions3last verified2026-04-18coverage score100%Postconditions — what we check
- ofetch · ofetch-no-try-catcherrorWhenawaited ofetch() call is not wrapped in try/catchThrows
FetchErrorRequired handlingCaller MUST wrap ofetch() in try/catch. Unlike native fetch(), ofetch automatically throws FetchError when response.ok is false (any non-2xx status). FetchError has .status, .statusCode, .data (parsed response body), .message, .request, and .response properties. Without try/catch, any 4xx or 5xx response causes an unhandled exception.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - ofetch · ofetch-timeout-unhandlederrorWhenofetch() called with the timeout option set (e.g., { timeout: 3000 }) and the request does not complete within the timeout window.Throws
FetchError wrapping a TimeoutError as its cause. The FetchError message includes "[TimeoutError]: The operation was aborted due to timeout". The cause error has name "TimeoutError" and code 23 (DOMException TIMEOUT_ERR). This is distinct from network-error FetchErrors which have no .cause.Required handlingCaller MUST wrap ofetch() in try/catch when using the timeout option: try { const data = await ofetch('/api/data', { timeout: 5000 }); } catch (error) { if (error.cause?.name === 'TimeoutError') { // Request timed out — retry, return cached, or surface error to user } throw error; } Without try/catch, a timeout causes an unhandled FetchError that crashes the calling async context. Timeout errors are NOT retried (retries are suppressed for timeout-triggered aborts per source: isAbort check excludes errors where context.options.timeout is set).costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - ofetch · ofetch-retry-post-disabledwarningWhenofetch() called with a POST, PUT, PATCH, or DELETE method (explicitly set or inferred from body presence) and the server returns a retryable status code (500, 502, 503, 504, 429, etc.) but the caller expects automatic retry.Throws
FetchError immediately after the first failure — no automatic retry occurs. The default retry count for POST/PUT/PATCH is 0 (disabled) to avoid unintended side effects from repeated writes.Required handlingFor idempotent POST/PUT operations that should retry, explicitly set retry: await ofetch('/api/resource', { method: 'POST', body: { ... }, retry: 3, // explicit retry count overrides the default retryDelay: 500, // ms between retries }); For non-idempotent writes, do NOT enable retry — repeated execution can cause duplicate records, double-charges, or duplicate emails. Wrap in try/catch and implement application-level retry with idempotency keys instead.costmediumin prodsilent failureusers seedegraded performancevisibilitysilent - $fetch · dollar-fetch-no-try-catcherrorWhenawaited $fetch() call is not wrapped in try/catchThrows
FetchErrorRequired handlingCaller MUST wrap $fetch() in try/catch. $fetch is an alias for ofetch and has identical behavior: automatically throws FetchError when response.ok is false. FetchError has .status, .statusCode, .data, .message, .request, .response properties.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - raw · raw-still-throws-on-errorerrorWhenofetch.raw() is called without try/catch and the server returns a 4xx or 5xx status code. Callers who switch from native fetch() to ofetch.raw() expecting the native fetch() behavior (no throw on non-2xx) will be surprised.Throws
FetchError — identical to ofetch(). The raw suffix refers to the return type (full FetchResponse object vs. just ._data), NOT to bypassing error handling. FetchError.response contains the full response; FetchError.data contains the parsed error body; FetchError.status contains the HTTP status code.Required handlingCaller MUST wrap ofetch.raw() in try/catch: try { const response = await ofetch.raw('/api/data'); console.log(response.status, response.headers.get('x-request-id')); const data = response._data; } catch (error) { // error is FetchError — use error.status, error.data, error.response if (error.status === 404) { ... } } To bypass error throwing (and inspect all responses including errors), use ignoreResponseError: true option — this makes ofetch.raw() behave like native fetch() and return the response for all status codes.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - raw · raw-timeout-unhandlederrorWhenofetch.raw() called with timeout option and the request does not complete within the specified timeout window.Throws
FetchError wrapping TimeoutError as cause. Identical timeout behavior to ofetch() — same $fetchRaw internal function, same error path.Required handlingSame as ofetch() with timeout: wrap in try/catch and check error.cause?.name === 'TimeoutError' to distinguish timeout from HTTP errors.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
Sources
Every postcondition cites at least one of these. Numbered to match the footnotes above.
- [1]github.com/unjs/ofetchhttps://github.com/unjs/ofetch
- [2]raw.githubusercontent.com/unjs/ofetchhttps://raw.githubusercontent.com/unjs/ofetch/main/README.md
- [3]github.com/unjs/ofetchhttps://github.com/unjs/ofetch/blob/main/src/fetch.ts
- [4]github.com/unjs/ofetchhttps://github.com/unjs/ofetch#-timeout
- [5]github.com/unjs/ofetchhttps://github.com/unjs/ofetch#-auto-retry
- [6]github.com/unjs/ofetchhttps://github.com/unjs/ofetch#-access-to-raw-response
Need a different package?
Request a profile