Fetch API & AbortController
Key Learning Objectives
Master Fetch API Promise semantics: understand why fetch does NOT reject on HTTP 4xx/5xx status codes.
Validate HTTP response status using response.ok (200-299) and response.status.
Implement request cancellation and timeout bounds using AbortController and AbortSignal.
Handle and discriminate AbortError from general network connection failures.
Implement resilient retry strategies with exponential backoff and stale-request cancellation for UI inputs.
The Interview Problem
What is logged to the console when the following network wrapper function executes with successful, HTTP 404, and timed-out requests using AbortController cancellation?
1async function fetchWithTimeout(mockApi, timeoutMs) {2 const controller = new AbortController();3 const timeoutId = setTimeout(() => controller.abort(), timeoutMs);45 try {6 const response = await mockApi(controller.signal);7 clearTimeout(timeoutId);89 if (!response.ok) {10 return 'HTTP-' + response.status;11 }12 return await response.json();13 } catch (err) {14 clearTimeout(timeoutId);15 if (err.name === 'AbortError') {16 return 'REQUEST_ABORTED';17 }18 return 'NETWORK_ERROR';19 }20}2122const okApi = async (signal) => ({23 ok: true,24 status: 200,25 json: async () => 'DATA_OK'26});2728const notFoundApi = async (signal) => ({29 ok: false,30 status: 404,31 json: async () => 'NOT_FOUND'32});3334const hangingApi = (signal) =>35 new Promise((_, reject) => {36 signal.addEventListener('abort', () => {37 const err = new Error('The operation was aborted');38 err.name = 'AbortError';39 reject(err);40 });41 });4243const res1 = await fetchWithTimeout(okApi, 100);44const res2 = await fetchWithTimeout(notFoundApi, 100);45const res3 = await fetchWithTimeout(hangingApi, 20);4647console.log(res1, res2, res3);
Predict Console Output
Select the option that matches what standard ECMAScript prints to the console:
DATA_OK HTTP-404 REQUEST_ABORTED
DATA_OK NETWORK_ERROR REQUEST_ABORTED
DATA_OK HTTP-404 NETWORK_ERROR
DATA_OK NOT_FOUND REQUEST_ABORTED
V8 Engine Execution Trace
Step 1 of 6 (Line 38)okApi resolves successfully with ok: true. Clears timer and returns 'DATA_OK'.
Deep Technical Breakdown
Fetch API Mental Model: The #1 Interview Misconception
The most pervasive misconception in frontend interviews is believing fetch() rejects when the server returns a 404 or 500 status. The Fetch Promise rejects ONLY on network failure or if the request was aborted.
// CORRECT PATTERN FOR ERROR HANDLING IN FETCH
async function safeFetch(url) {
const res = await fetch(url);
if (!res.ok) {
throw new Error(`HTTP Error: ${res.status} ${res.statusText}`);
}
return await res.json();
}Request Cancellation with AbortController
Modern JavaScript applications use AbortController to cancel in-flight network requests:
- Timeout Cancellation: Cancel slow requests using
AbortSignal.timeout(5000)(supported in modern browsers and Node.js 18+). - Stale Request Cancellation in UI Autocomplete: When a user types into a search input, cancel previous pending queries to prevent race conditions (where an older slow query overwrites a newer fast query):
let currentController = null;
async function onSearchInput(query) {
if (currentController) {
currentController.abort(); // Cancel previous in-flight request
}
currentController = new AbortController();
try {
const res = await fetch(`/api/search?q=${query}`, { signal: currentController.signal });
const data = await res.json();
renderResults(data);
} catch (err) {
if (err.name !== 'AbortError') {
showError(err);
}
}
}Response Body Consumption Rule: bodyUsed
A Fetch Response body is a ReadableStream that can only be read once. Calling await res.json() and subsequently await res.text() throws TypeError: Failed to execute 'text' on 'Response': body stream already read. If you need to read the body multiple times, use const clone = res.clone() beforehand.
Common Traps & Mistakes
Relying on `try/catch` to capture HTTP 404 or 500 responses. Fetch resolves on HTTP error statuses; you must explicitly check `res.ok`.
Attempting to read the response body multiple times (`res.json()` then `res.text()`) without cloning the response stream.
Failing to check `err.name === 'AbortError'`, accidentally displaying user-facing error banners when a request was intentionally cancelled.
Forgetting to clear timeout timers (`clearTimeout(timeoutId)`) when the fetch completes early, leaking timer handles.
FAANG Follow-Up Probes
Probe #1
How does AbortSignal.any([signal1, signal2]) in modern browsers allow cancelling an operation via either a user cancel button or a timeout?
Probe #2
How would you implement exponential backoff with jitter for transient 503 Service Unavailable network errors?
Probe #3
Why does fetch not include cookies in cross-origin requests by default, and what does credentials: 'include' do?
