Difficulty: Intermediate
Explain the key Web APIs: localStorage, sessionStorage, and fetch. How do you use them effectively?
Web APIs provide browser capabilities accessible through JavaScript. The most commonly used in interviews are Storage APIs and the Fetch API.
localStorage persists data with no expiration. sessionStorage persists only for the browser tab session. Both store key-value pairs as strings (5-10MB limit). Use JSON.stringify/parse for objects.
The Fetch API is the modern way to make HTTP requests, replacing XMLHttpRequest. It returns Promises, supports streaming, AbortController for cancellation, and various request options. Key gotcha: fetch does NOT reject on HTTP errors (404, 500) - you must check response.ok.
Other important Web APIs include: IntersectionObserver (lazy loading), ResizeObserver (element size changes), MutationObserver (DOM changes), requestAnimationFrame (smooth animations), Web Workers (background threads), and the Clipboard API.
// Basic operations
localStorage.setItem('name', 'Alice');
console.log(localStorage.getItem('name')); // 'Alice'
// Storing objects (must serialize)
const user = { id: 1, name: 'Alice', prefs: { theme: 'dark' } };
localStorage.setItem('user', JSON.stringify(user));
const stored = JSON.parse(localStorage.getItem('user'));
console.log(stored.prefs.theme); // 'dark'
// Safe getter with fallback
function getStored(key, fallback = null) {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : fallback;
} catch {
return fallback;
}
}
console.log(getStored('missing', { default: true }));
// Storage with expiry
function setWithExpiry(key, value, ttlMs) {
const item = { value, expiry: Date.now() + ttlMs };
localStorage.setItem(key, JSON.stringify(item));
}
function getWithExpiry(key) {
const item = JSON.parse(localStorage.getItem(key));
if (!item || Date.now() > item.expiry) {
localStorage.removeItem(key);
return null;
}
return item.value;
}
setWithExpiry('token', 'abc123', 3600000); // 1 hour
console.log(getWithExpiry('token')); // 'abc123'
localStorage persists across browser sessions. Always wrap in try/catch (private browsing may throw). The expiry wrapper adds TTL functionality that localStorage lacks natively.
// Basic GET request
async function fetchUsers() {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
// IMPORTANT: fetch does NOT throw on 404/500!
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const users = await response.json();
console.log(`Fetched ${users.length} users`);
return users;
}
// POST with JSON body
async function createUser(userData) {
const response = await fetch('https://jsonplaceholder.typicode.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
},
body: JSON.stringify(userData)
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
}
// Wrapper with timeout and retries
async function fetchWithRetry(url, options = {}, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const res = await fetch(url, options);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (err) {
if (i === retries - 1) throw err;
console.log(`Retry ${i + 1}/${retries}`);
await new Promise(r => setTimeout(r, 1000 * (i + 1)));
}
}
}
console.log('fetch does NOT reject on HTTP errors - always check response.ok');
fetch only rejects on network failures, NOT HTTP errors. Always check response.ok. POST requires explicit Content-Type header and JSON.stringify for the body.
// Cancel a fetch request
async function fetchWithTimeout(url, timeoutMs = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, {
signal: controller.signal
});
clearTimeout(timeoutId);
return await response.json();
} catch (err) {
clearTimeout(timeoutId);
if (err.name === 'AbortError') {
console.log('Request timed out');
} else {
throw err;
}
}
}
// Cancel multiple requests at once
class ApiClient {
constructor() {
this.controller = null;
}
async search(query) {
// Cancel previous search
if (this.controller) this.controller.abort();
this.controller = new AbortController();
try {
const res = await fetch(`/api/search?q=${query}`, {
signal: this.controller.signal
});
return await res.json();
} catch (err) {
if (err.name !== 'AbortError') throw err;
console.log('Previous search cancelled');
}
}
}
const client = new ApiClient();
// Rapid searches - previous ones are automatically cancelled
client.search('hel'); // cancelled
client.search('hell'); // cancelled
client.search('hello'); // completes
console.log('AbortController cancels in-flight requests');
AbortController cancels fetch requests by passing its signal. Aborted fetches throw an AbortError. This prevents race conditions in search-as-you-type features.
localStorage, sessionStorage, fetch, AbortController, Web Storage, JSON