4 min read
Browser APIs
Fetch & HTTP
js// Basic GET
const res = await fetch('/api/users')
if (!res.ok) throw new Error(`HTTP ${res.status}`)
const data = await res.json()
// POST with JSON body
const res = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Alice' }),
signal: AbortSignal.timeout(5000), // auto-abort after 5s
})
// With AbortController (manual cancel)
const controller = new AbortController()
const res = await fetch('/api/stream', { signal: controller.signal })
controller.abort() // cancel request
// Response methods (consume body once)
res.json() // parse JSON
res.text() // raw text
res.blob() // binary data
res.arrayBuffer()
res.formData()
res.headers.get('content-type')
res.status // 200, 404, etc.
res.ok // true if 200-299Parallel requests
js// All resolve or all reject
const [users, posts] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
])
// Settle regardless of failure
const results = await Promise.allSettled([...])
results.forEach(r => {
if (r.status === 'fulfilled') console.log(r.value)
else console.error(r.reason)
})Web Storage
js// localStorage — persists until explicitly cleared
localStorage.setItem('theme', 'dark')
localStorage.getItem('theme') // 'dark'
localStorage.removeItem('theme')
localStorage.clear()
// sessionStorage — cleared when tab closes
sessionStorage.setItem('draft', JSON.stringify(data))
const draft = JSON.parse(sessionStorage.getItem('draft') ?? 'null')
// Both are synchronous and string-only (~5MB limit)
// For complex data, serialize/deserialize with JSON
// Listen for changes from other tabs
window.addEventListener('storage', e => {
console.log(e.key, e.oldValue, e.newValue) // localStorage only
})Cookies
js// Read all cookies
document.cookie // "theme=dark; user=alice"
// Set cookie
document.cookie = 'theme=dark; path=/; max-age=86400; SameSite=Lax; Secure'
// Attributes
// path=/ — accessible from all paths
// max-age=N — seconds until expiry (overrides expires)
// expires=date — specific expiry date
// Secure — HTTPS only
// HttpOnly — not accessible via JS (server-set only, prevents XSS)
// SameSite=Lax — sent on same-site + top-level cross-site navigation
// SameSite=Strict — only same-site requests
// SameSite=None; Secure — cross-site (third-party cookies)URL & History
js// URL parsing
const url = new URL('https://example.com/search?q=hello&page=2#results')
url.hostname // 'example.com'
url.pathname // '/search'
url.searchParams.get('q') // 'hello'
url.searchParams.set('page', '3')
url.searchParams.append('filter', 'new')
url.hash // '#results'
url.toString() // full URL string
// History API (SPA routing)
history.pushState({ page: 2 }, '', '/search?page=2') // add entry
history.replaceState({ page: 2 }, '', '/search?page=2') // replace current
history.back()
history.forward()
history.go(-2)
window.addEventListener('popstate', e => {
console.log(e.state) // state object passed to pushState
})Clipboard API
js// Write
await navigator.clipboard.writeText('Hello world')
// Read (requires user permission or gesture)
const text = await navigator.clipboard.readText()
// Copy on button click
document.querySelector('#copy').addEventListener('click', async () => {
await navigator.clipboard.writeText(document.querySelector('#output').value)
})Geolocation
jsnavigator.geolocation.getCurrentPosition(
position => {
const { latitude, longitude, accuracy } = position.coords
},
error => {
// error.code: 1=PERMISSION_DENIED, 2=UNAVAILABLE, 3=TIMEOUT
console.error(error.message)
},
{ enableHighAccuracy: true, timeout: 10000, maximumAge: 0 }
)
// Watch (continuous updates)
const id = navigator.geolocation.watchPosition(success, error, options)
navigator.geolocation.clearWatch(id)Notifications API
js// Request permission
const permission = await Notification.requestPermission()
// 'granted' | 'denied' | 'default'
if (permission === 'granted') {
new Notification('New message', {
body: 'Alice: Hey!',
icon: '/icon.png',
badge: '/badge.png',
})
}ResizeObserver
Watch element size changes without polling — better than window.resize for components.
jsconst observer = new ResizeObserver(entries => {
for (const entry of entries) {
const { width, height } = entry.contentRect
console.log(`${entry.target.id}: ${width}×${height}`)
}
})
observer.observe(document.querySelector('.chart'))
observer.unobserve(el)
observer.disconnect()requestAnimationFrame
Sync with browser paint cycle for smooth animations.
jsfunction animate(timestamp) {
// timestamp is DOMHighResTimeStamp (ms)
el.style.transform = `translateX(${Math.sin(timestamp / 1000) * 100}px)`
requestAnimationFrame(animate)
}
const id = requestAnimationFrame(animate)
cancelAnimationFrame(id)
// Better pattern: stop when done
function runAnimation() {
let start = null
const duration = 500
function step(timestamp) {
if (!start) start = timestamp
const progress = Math.min((timestamp - start) / duration, 1)
el.style.opacity = progress
if (progress < 1) requestAnimationFrame(step)
}
requestAnimationFrame(step)
}Web Workers
Offload heavy computation off the main thread.
js// main.js
const worker = new Worker('./worker.js')
worker.postMessage({ data: largeArray })
worker.addEventListener('message', e => {
console.log('Result:', e.data)
})
worker.addEventListener('error', e => {
console.error(e.message)
})
worker.terminate()
// worker.js
self.addEventListener('message', e => {
const result = heavyComputation(e.data)
self.postMessage(result)
})Limitations: no DOM access, no window, limited APIs. Use SharedArrayBuffer + Atomics for shared memory between workers.
IndexedDB (via idb wrapper)
Client-side database — structured data, large storage, indexed queries.
jsimport { openDB } from 'idb'
const db = await openDB('my-app', 1, {
upgrade(db) {
const store = db.createObjectStore('notes', { keyPath: 'id', autoIncrement: true })
store.createIndex('by-date', 'createdAt')
},
})
// Write
await db.add('notes', { title: 'Hello', body: '...', createdAt: new Date() })
await db.put('notes', { id: 1, title: 'Updated', body: '...', createdAt: new Date() })
// Read
const note = await db.get('notes', 1)
const all = await db.getAll('notes')
const byDate = await db.getAllFromIndex('notes', 'by-date')
// Delete
await db.delete('notes', 1)
await db.clear('notes')Performance API
js// High-resolution timestamps
const start = performance.now()
doWork()
console.log(performance.now() - start, 'ms')
// Mark and measure
performance.mark('start-render')
render()
performance.mark('end-render')
performance.measure('render', 'start-render', 'end-render')
const [measure] = performance.getEntriesByName('render')
console.log(measure.duration)
// Navigation timing
const nav = performance.getEntriesByType('navigation')[0]
console.log(nav.domContentLoadedEventEnd - nav.startTime) // DOMContentLoaded time
console.log(nav.loadEventEnd - nav.startTime) // load time
// Resource timing
performance.getEntriesByType('resource').forEach(r => {
console.log(r.name, r.duration)
})[prev·next]