learn
JavaScript

Fetch, CORS and browser storage

Await-await-ok, CORS in practice, and cookies versus storages.

fetch resolves on HTTP errors, browsers gate cross-origin reads, and storage is strings-only — three facts that decide whether data code works. Interviewers ask this because "await, await, ok" plus a CORS explanation covers most real data bugs.

Real-life situation

A profile loader awaits the user, then the avatar, then renders — but a 404 renders as a broken page instead of "user missing", because fetch never rejected. Checking res.ok turns the silent bad render into a handled empty state. Meanwhile a search box aborts its previous request on every keystroke, so slow replies can't overwrite fresh ones.

Visual

Browser storage
BrowserAPIRealtimeCacheDatabaseShareddurableephemeral

Try it

Fetch with guards
javascriptTab moves to the next control
async function loadUser(id) {
const res = await fakeFetch("/users/" + id, { latencyMs: 5 });
if (!res.ok) {
  return "user missing:" + res.status;
}
const user = await res.json();
return user.name;
}
console.log(await loadUser(1));
console.log(await loadUser(99));
async function loadAbort() {
const controller = new AbortController();
const pending = fakeFetch("/users", {
  latencyMs: 50,
  signal: controller.signal,
});
controller.abort();
try {
  await pending;
  return "arrived";
} catch (error) {
  return error.name;
}
}
console.log(await loadAbort());
Press Run to see the output.
Expected output
Aarav Sharma
user missing:404
AbortError
Runner helpers

sleep(ms) waits; fakeFetch(url) returns deterministic sample data. Console code runs in a worker with a 5-second limit.

Try pointing the first call at /flaky and watch it throw instead of returning. Then give loadAbort no abort and confirm "arrived".

Storage is strings
javascriptTab moves to the next control
function saveDraft(text) {
return JSON.stringify({ text: text });
}
function loadDraft(raw) {
try {
  return JSON.parse(raw).text;
} catch (error) {
  return "empty";
}
}
const raw = saveDraft("hi");
console.log(loadDraft(raw));
console.log(loadDraft("###"));
console.log(loadDraft(null));
Press Run to see the output.
Expected output
hi
empty
empty
Runner helpers

sleep(ms) waits; fakeFetch(url) returns deterministic sample data. Console code runs in a worker with a 5-second limit.

Cookies versus storages

Cookies travel with every request (sessions, auth flags) and hide behind HttpOnly. localStorage persists per origin without traveling (themes, drafts). sessionStorage dies with the tab (wizard state). Secrets go in HttpOnly cookies, never in readable storage — one paragraph on XSS: build markup only from trusted strings, or attacker text becomes your code.

Fetch result shape — guess first, then reveal.

predict.js
async function status(id) {
const res = await fakeFetch("/users/" + id, { latencyMs: 5 });
return res.ok ? "ok" : "fail:" + res.status;
}
status(2).then(function (s) {
console.log(s);
});

Interview questions

What does fetch return and what must you check yourself?basic

A promise of a Response — await it, then await the body (res.json()), then check res.ok, because fetch resolves on HTTP ERRORS (404, 500) and rejects only on network failure. Three steps, three classic bugs: one await (body never read), no ok check (errors treated as data), no catch (offline crashes). Recite "await, await, ok" and you will never ship the broken version.

Interviewers also ask

  • What are the three classic fetch bugs?
How do GET, POST, PUT, PATCH and DELETE differ in practice?intermediate

GET reads (cacheable, no body); POST creates or triggers actions; PUT replaces a whole resource; PATCH partially updates it; DELETE removes. Browsers and caches treat GET as safe to repeat and retry — never mutate through GET, or refreshes and prefetchers will fire your writes twice. Match the verb to the intent and say why; REST literacy is what is being tested, not memorized definitions.

Interviewers also ask

  • Why must GET never mutate?
What is CORS in one practical paragraph?intermediate

Browsers block pages from reading responses of OTHER origins unless that server opts in with allow-origin headers. Your fetch still SENDS (so mutations can happen), but your code can't READ the reply — the classic "works in curl, fails in the browser". Fixes live server-side (headers, a same-origin proxy); browser-side workarounds are hacks. Say "server problem, not a fetch bug" first.

Interviewers also ask

  • Why does the same request work in curl?
Cookies vs localStorage vs sessionStorage: when do you use each?basic

Cookies: tiny (~4KB), sent with EVERY request automatically — sessions and auth tokens, with HttpOnly/Secure/SameSite flags doing the real security work. localStorage: ~5MB, never sent, survives restarts — themes, drafts, caches. sessionStorage: same API, dies with the tab — per-tab wizard state. Never store secrets where JS can read them if a cookie flag could hide them instead.

Interviewers also ask

  • Where should an auth token live?
What is XSS in one paragraph, and what is the one rule that stops it?intermediate

Cross-site scripting: attacker text reaching the page as CODE — usually via innerHTML with user strings — running with your site's privileges (cookie theft, action forgery). The one rule: never build markup from untrusted strings; use textContent and let the framework escape. Second layer: HttpOnly cookies (stolen-session damage control) and a Content Security Policy. Interviews accept "textContent, HttpOnly, CSP" as the complete answer.

Interviewers also ask

  • What does HttpOnly actually protect?
How do you store an object in localStorage?basic

localStorage holds STRINGS only, so JSON.stringify on the way in and JSON.parse on the way out, wrapped in try/catch (private mode and quota errors throw). Read with a fallback default for first visits, and version your keys (cart:v1) so schema changes don't resurrect ancient shapes. Anything sensitive or large belongs server-side, not in the browser store.

Interviewers also ask

  • What breaks when the stored schema changes?
How do you keep two tabs' localStorage views in sync?intermediate

Listen for the storage event: it fires in every OTHER tab when one tab writes, carrying the key and new value — re-read and re-render on it. It does NOT fire in the writing tab itself, and it never fires for sessionStorage or cookies. For same-tab reactivity, wrap your setter to notify locally too. Name the event and its blind spots; that is the whole interview answer.

Interviewers also ask

  • Does the writing tab get the event?

Key takeaways

  • fetch needs two awaits plus an ok check; it rejects only on network failure.
  • CORS failures are server configuration, not fetch bugs.
  • Cookies travel, storages stay, sessions die with the tab.

References

Last verified: 2026-10-12.

On this page