In React Native, fetch() has no timeout option; how do you stop a request hanging on a bad mobile network, and what does aborting cancel?
answer
- no timeout key in the fetch options
- AbortController plus a timer
- clearTimeout in finally
- AbortSignal.timeout in 0.87
- abort cancels the native request
basics
~20 sPass an AbortController's signal to fetch and call abort() from a setTimeout, clearing the timer in finally; React Native 0.87 also has AbortSignal.timeout(ms). Aborting rejects the promise with an abort error and cancels the underlying native HTTP request.
solid answer
~40 s`fetch` has no `timeout` option, and you should not rely on a platform default: React Native's Android OkHttp client is built with connect, read and write timeouts of `0`, meaning no limit. So I create an `AbortController`, pass `controller.signal` to `fetch`, start a `setTimeout` that calls `controller.abort()`, and clear the timer in `finally` so it cannot fire later. In React Native 0.87, `AbortSignal.timeout(ms)` and `AbortSignal.any([...])` are built in, which lets me combine a timeout with a caller's cancel signal. When the signal fires, the promise rejects with an abort error and the native request is cancelled, not just ignored. Libraries built on `XMLHttpRequest`, such as axios, expose a `timeout` setting instead, which is passed down to the native request.
code
typescript · 13 linesexport async function getJson<T>(url: string, ms = 10_000): Promise<T> {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), ms);
try {
const res = await fetch(url, { signal: controller.signal });
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
return (await res.json()) as T;
} finally {
clearTimeout(timer);
}
}go deeper
Recall that fetch has no timeout option and that an AbortController signal plus a timer is how you give up on a request.
Explain the full pattern with clearTimeout in finally, AbortSignal.timeout and any on React Native 0.87, and that abort cancels the native request underneath.
Put timeouts in one request layer with per-call deadlines, distinguish user cancellation from timeouts, and pair timeouts with a retry policy that respects idempotency.
Set a network budget for the app: deadlines per request class, retry and backoff rules, and what the UI promises users on slow networks, enforced by one shared client.
## Why a timeout is your job The Fetch API has no `timeout` field in its options object, and mobile networks fail in ways desktop networks rarely do: a train enters a tunnel, the phone hops from Wi-Fi to cellular, a captive portal swallows packets. A request can then sit **pending** with a spinner on screen. Do not count on a platform default to rescue you. React Native's Android networking builds its OkHttp client with **connect, read and write timeouts of `0`, which OkHttp treats as no limit**. React Native's `XMLHttpRequest` also starts with `timeout = 0`. The only bound you can trust is the one you set. ## The standard pattern 1. Create an `AbortController` per request. 2. Pass `controller.signal` as the `signal` option of `fetch`. 3. Start a timer that calls `controller.abort()`. 4. Clear the timer in `finally`, whether the request succeeded, failed or was aborted. 5. Keep the body read (`response.json()`) inside the same `try` if you want the deadline to cover the whole exchange. In **React Native 0.87**, the built-in `AbortSignal` also implements `AbortSignal.timeout(ms)`, which aborts itself with a `TimeoutError` reason, and `AbortSignal.any(signals)`, which fires when any input signal fires. Together they express "abort on timeout **or** when the user leaves the screen" without hand-wiring listeners. ## What abort actually does It helps to know the layers: - **React Native's built-in `fetch`** is a polyfill over React Native's `XMLHttpRequest`. Aborting calls `xhr.abort()`, which asks the native networking module to **cancel the request** (`abortRequest`). The `NSURLSession` task or OkHttp call is torn down, so bytes stop flowing and the socket is released. - **`expo/fetch`**, the global `fetch` in Expo SDK 56 and later, also listens for the signal, errors the response body stream and cancels its native request. - The `fetch` promise **rejects**. Code that shows an error message should distinguish a deliberate cancel (user left the screen) from a timeout; the rejection's shape differs between implementations, so decide from `signal.reason` or your own flag rather than parsing messages. Aborting stops the client. It does **not** undo work the server has already started, which is why non-idempotent calls need their own retry rules. ## Choosing where to put the logic | Option | Timeout mechanism | Notes | |---|---|---| | Built-in `fetch` | `AbortController` + timer, or `AbortSignal.timeout` | No dependency; you write the wrapper once | | `XMLHttpRequest` | `xhr.timeout` (ms) plus `ontimeout` | Value is passed to the native request | | axios | `timeout` in the request or instance config | Runs on React Native's XHR; also accepts a `signal` | Whichever you choose, put it in **one request helper** so every screen gets the same deadline, and pick values per call type: a search-as-you-type request can give up quickly, while a large upload needs far longer. ## Cancelling when the user leaves a screen Timeouts cover a slow network; cancellation covers a user who has moved on. On a React Native screen the usual shape is to create a controller when the screen starts loading, pass its signal to the request helper, and call `abort()` in the effect's cleanup. Combined with a deadline through `AbortSignal.any`, one signal then means "stop if the user leaves **or** time runs out". This matters more on mobile than on the web: a stack navigator keeps previous screens mounted, while a user who swipes back from a detail screen unmounts it. Without cancellation, a slow response lands after the screen is gone, the state update is wasted, and on a metered cellular connection the user paid for bytes nobody will see. ## Common mistakes - Forgetting `clearTimeout`, so a timer from a finished request aborts nothing but keeps a closure alive, or, worse, a shared controller aborts the next request. - Reusing one `AbortController` for several requests; once aborted, a signal stays aborted forever. - Treating every abort as a network error and showing a red banner when the user simply navigated away. - Assuming the operating system will end a stuck request quickly on its own. In an interview, name the missing option, show the controller-plus-timer pattern, mention `AbortSignal.timeout` on current React Native, and explain that abort cancels the native request but not server-side effects.
- How do you tell a timeout from the user navigating away when the fetch promise rejects?Inspect why the signal fired rather than the error text. `AbortSignal.timeout` aborts with a `TimeoutError` reason, while your own `controller.abort()` uses the default reason or one you pass. Check `signal.reason` (or a flag you set) and show an error only for timeouts; silently ignore user cancellations.
- After a timeout you want to retry. Which requests is that safe for?Reads and idempotent writes can be retried, ideally with exponential backoff and jitter and a small attempt cap. For a write that may have reached the server, retry only if the API supports an idempotency key; otherwise the abort may have left the operation half-done.
- How does axios expose a timeout in a React Native app?axios accepts a `timeout` value in milliseconds in the instance or request config and rejects when it elapses. In React Native it runs on the built-in `XMLHttpRequest`, whose `timeout` is passed to the native request, and it also accepts an `AbortController` `signal` for cancellation.
saying these in an interview costs you the question
- fetch in React Native accepts a timeout option in milliseconds.
- The native client times out stuck requests after a short default anyway.
- Aborting only makes the promise reject; the native request keeps downloading.
- One AbortController can be shared and reused across many requests.
- Aborting a POST guarantees the server did not process it.