# JavaScript Web APIs — Deep Dive
> Comprehensive research on browser APIs beyond vanilla JavaScript.
---
## 1. Fetch API
### Beyond `fetch()`
```javascript
// Basic usage with error handling
async function jsonFetch(url, options = {}) {
const response = await fetch(url, {
headers: { 'Accept': 'application/json' },
...options,
});
if (!response.ok) {
throw new HttpError(response.status, await response.text());
}
return response.json();
}
// Request with timeout
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
try {
const data = await fetch('/api/data', {
signal: controller.signal,
}).then(r => r.json());
} finally {
clearTimeout(timeoutId);
}
```
### Request Cloning
```javascript
// You can only read a response body once
const response = await fetch('/api/data');
// Clone to read multiple times
const clone = response.clone();
const text = await response.text();
const json = await clone.json(); // Works on clone
```
### Request/Response Objects
```javascript
// Create request manually
const request = new Request('/api/data', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ key: 'value' }),
});
// Modify a request
const modified = new Request(request, {
headers: { ...request.headers, 'Authorization': 'Bearer token' },
});
// Create response
const response = new Response(JSON.stringify({ ok: true }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
```
---
## 2. Streams API
### Readable Streams
```javascript
// Read file as stream
const fileInput = document.getElementById('file');
const file = fileInput.files[0];
const stream = file.stream(); // ReadableStream
const reader = stream.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
console.log('Chunk:', value); // Uint8Array
}
```
### Transform Streams
```javascript
// TextDecoder transform
const decoder = new TextDecoderStream();
const reader = file.stream().pipeThrough(decoder).getReader();
// Custom transform — uppercase text
const upperCase = new TransformStream({
transform(chunk, controller) {
controller.enqueue(chunk.toString().toUpperCase());
},
});
// Compression
const compressed = await compressStream(response.body, 'deflate');
// Pipeline
const result = await fetch('/data.json')
.then(res => res.body)
.then(body => body.pipeThrough(new TextDecoderStream()))
.then(stream => stream.pipeTo(destination));
```
### Writing Streams
```javascript
// Write to a blob
const writable = new WritableStream({
write(chunk) {
// Process each chunk
},
close() {
console.log('Stream complete');
},
abort(err) {
console.error('Stream aborted:', err);
},
});
const writer = writable.getWriter();
writer.write('Hello');
writer.write(' World');
writer.close();
```
---
## 3. Service Workers
### Lifecycle
```
Register → Install → Activate → Control pages → Handle fetch events
```
### Basic Service Worker
```javascript
// sw.js
const CACHE_NAME = 'app-v1';
const ASSETS = ['/', '/index.html', '/styles.css', '/app.js'];
// Install — cache assets
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache => cache.addAll(ASSETS))
);
self.skipWaiting(); // Activate immediately
});
// Activate — clean old caches
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then(keys =>
Promise.all(
keys.filter(key => key !== CACHE_NAME)
.map(key => caches.delete(key))
)
)
);
self.clients.claim(); // Claim all pages
});
// Fetch — cache-first with network fallback
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then(cached => {
if (cached) return cached;
return fetch(event.request).then(response => {
// Don't cache non-successful responses
if (!response.ok) return response;
const clone = response.clone();
caches.open(CACHE_NAME).then(cache => cache.put(event.request, clone));
return response;
});
})
);
});
```
### Registering
```javascript
// main.js
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js', { scope: '/' })
.then(reg => console.log('SW registered:', reg.scope))
.catch(err => console.error('SW failed:', err));
}
```
### Push Notifications
```javascript
// Request permission
Notification.requestPermission().then(permission => {
if (permission === 'granted') {
// Subscribe
navigator.serviceWorker.ready.then(reg => {
reg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(VAPID_KEY),
});
});
}
});
// Handle push event in service worker
self.addEventListener('push', (event) => {
const data = event.data?.json() || { title: 'Notification', body: 'Hello' };
event.waitUntil(
self.registration.showNotification(data.title, {
body: data.body,
icon: '/icon.png',
badge: '/badge.png',
actions: [
{ action: 'view', title: 'View' },
{ action: 'dismiss', title: 'Dismiss' },
],
})
);
});
```
---
## 4. WebSockets
### Real-Time Communication
```javascript
const ws = new WebSocket('wss://example.com/socket');
ws.onopen = () => {
console.log('Connected');
ws.send(JSON.stringify({ type: 'subscribe', channel: 'chat' }));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Received:', data);
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
ws.onclose = (event) => {
console.log('Disconnected:', event.code, event.reason);
// Auto-reconnect
if (event.code !== 1000) {
setTimeout(() => connect(), 1000);
}
};
```
### Reconnection with Exponential Backoff
```javascript
class WebSocketClient {
constructor(url, maxRetries = 5) {
this.url = url;
this.maxRetries = maxRetries;
this.retryCount = 0;
this.listeners = new Map();
this.connect();
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
console.log('Connected');
this.retryCount = 0;
};
this.ws.onmessage = (event) => {
const message = JSON.parse(event.data);
this.listeners.get(message.type)?.forEach(fn => fn(message.data));
};
this.ws.onclose = () => {
if (this.retryCount < this.maxRetries) {
const delay = Math.min(1000 * 2 ** this.retryCount, 30000);
this.retryCount++;
setTimeout(() => this.connect(), delay);
}
};
}
on(type, fn) {
if (!this.listeners.has(type)) this.listeners.set(type, []);
this.listeners.get(type).push(fn);
return () => this.off(type, fn);
}
off(type, fn) {
const fns = this.listeners.get(type);
this.listeners.set(type, fns.filter(f => f !== fn));
}
send(type, data) {
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({ type, data }));
}
}
}
const client = new WebSocketClient('wss://example.com/socket');
client.on('message', console.log);
client.send('ping', { time: Date.now() });
```
---
## 5. Web Workers
### Dedicated Worker
```javascript
// main.js
const worker = new Worker('./math-worker.js');
worker.postMessage({ operation: 'fibonacci', n: 40 });
worker.onmessage = (e) => console.log('Result:', e.data);
worker.onerror = (e) => console.error('Worker error:', e.message);
// math-worker.js
self.onmessage = (e) => {
const { operation, n } = e.data;
switch (operation) {
case 'fibonacci':
self.postMessage(fibonacci(n));
break;
case 'prime-sieve':
self.postMessage(sieveOfEratosthenes(n));
break;
}
};
function fibonacci(n) {
let a = 0, b = 1;
for (let i = 2; i <= n; i++) {
[a, b] = [b, a + b];
}
return b;
}
```
### Shared Worker
```javascript
// Multiple tabs share a single worker
const worker = new SharedWorker('./shared.js');
worker.port.start();
worker.port.postMessage({ type: 'greet', name: 'Alice' });
worker.port.onmessage = (e) => console.log(e.data);
// shared.js
self.onconnect = (e) => {
const port = e.ports[0];
port.onmessage = (event) => {
if (event.data.type === 'greet') {
port.postMessage(`Hello, ${event.data.name}!`);
}
};
};
```
### Web Worker with `import()` (Module Workers)
```javascript
const worker = new Worker('./worker.mjs', { type: 'module' });
// worker.mjs
import { process } from './lib.js';
self.onmessage = (e) => {
const result = process(e.data);
self.postMessage(result);
};
```
---
## 6. Storage APIs
### localStorage / sessionStorage
```javascript
// Typed storage wrapper
const Storage = {
set(key, value) {
localStorage.setItem(key, JSON.stringify(value));
},
get(key) {
const raw = localStorage.getItem(key);
return raw ? JSON.parse(raw) : null;
},
remove(key) {
localStorage.removeItem(key);
},
clear() {
localStorage.clear();
},
};
Storage.set('user', { name: 'Alice', role: 'admin' });
Storage.get('user'); // { name: 'Alice', role: 'admin' }
```
### IndexedDB
```javascript
class Database {
constructor(name, version) {
this.name = name;
this.version = version;
}
open() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.name, this.version);
request.onupgradeneeded = (event) => {
const db = event.target.result;
// Create object stores
if (!db.objectStoreNames.contains('users')) {
const store = db.createObjectStore('users', { keyPath: 'id' });
store.createIndex('email', 'email', { unique: true });
store.createIndex('name', 'name');
}
};
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async get(storeName, key) {
const db = await this.open();
return new Promise((resolve, reject) => {
const tx = db.transaction(storeName, 'readonly');
const store = tx.objectStore(storeName);
const req = store.get(key);
req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error);
});
}
async put(storeName, data) {
const db = await this.open();
return new Promise((resolve, reject) => {
const tx = db.transaction(storeName, 'readwrite');
tx.objectStore(storeName).put(data);
tx.oncomplete = () => resolve();
tx.onerror = () => reject(tx.error);
});
}
async getAll(storeName) {
const db = await this.open();
return new Promise((resolve, reject) => {
const tx = db.transaction(storeName, 'readonly');
const req = tx.objectStore(storeName).getAll();
req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error);
});
}
}
// Usage
const db = new Database('MyApp', 1);
await db.put('users', { id: 1, name: 'Alice', email: 'a@example.com' });
const user = await db.get('users', 1);
```
### Cache API
```javascript
// For service workers, but also accessible in main thread
const cache = await caches.open('my-cache');
await cache.put(new Request('/api/data'), response);
// Read from cache
const cached = await cache.match('/api/data');
if (cached) {
const data = await cached.json();
}
// Delete from cache
await cache.delete('/api/data');
// List all cache names
const names = await caches.keys(); // ['my-cache', 'app-v1']
```
---
## 7. Media APIs
### MediaDevices (Camera & Microphone)
```javascript
// Get user media
const stream = await navigator.mediaDevices.getUserMedia({
video: { width: 1280, height: 720, facingMode: 'user' },
audio: true,
});
// Display in <video> element
const video = document.getElementById('video');
video.srcObject = stream;
video.play();
// Capture photo from video
function takePhoto() {
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const ctx = canvas.getContext('2d');
ctx.drawImage(video, 0, 0);
canvas.toBlob(blob => {
const url = URL.createObjectURL(blob);
// Download or display
});
}
// List available devices
const devices = await navigator.mediaDevices.enumerateDevices();
devices.forEach(device => {
console.log(`${device.kind}: ${device.label || '(no label)'}`);
});
```
### MediaRecorder
```javascript
// Record audio/video
const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
const recorder = new MediaRecorder(stream, { mimeType: 'video/webm' });
const chunks = [];
recorder.ondataavailable = (e) => chunks.push(e.data);
recorder.onstop = () => {
const blob = new Blob(chunks, { type: 'video/webm' });
const url = URL.createObjectURL(blob);
console.log('Recording URL:', url);
};
recorder.start();
// ... later ...
recorder.stop();
```
### AudioContext (Web Audio API)
```javascript
// Create audio context
const ctx = new AudioContext();
// Analyze audio frequency
const analyser = ctx.createAnalyser();
analyser.fftSize = 256;
const source = ctx.createMediaStreamSource(stream);
source.connect(analyser);
// Get frequency data
const bufferLength = analyser.frequencyBinCount;
const data = new Uint8Array(bufferLength);
analyser.getByteFrequencyData(data);
// Visualize
const bars = document.querySelectorAll('.bar');
data.forEach((value, i) => {
if (bars[i]) {
bars[i].style.height = `${value / 255 * 100}%`;
}
});
```
### Media Source Extensions (MSE)
```javascript
// Dynamic video playback
const mediaSource = new MediaSource();
const video = document.getElementById('video');
video.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', () => {
const sourceBuffer = mediaSource.addSourceBuffer('video/mp4; codecs="avc1.42E01E"');
// Fetch and append video chunks
fetch('/video.mp4').then(res => res.arrayBuffer()).then(buffer => {
sourceBuffer.appendBuffer(buffer);
});
});
```
---
## 8. Performance APIs
### Performance Observer
```javascript
// Observe Largest Contentful Paint
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
console.log(`LCP: ${entry.startTime}ms`);
}
}).observe({ type: 'largest-contentful-paint', buffered: true });
// Observe Cumulative Layout Shift
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
if (!entry.hadRecentInput) {
console.log(`CLS: ${entry.value}`);
}
}
}).observe({ type: 'layout-shift', buffered: true });
// Observe First Input Delay
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
console.log(`FID: ${entry.processingStart - entry.startTime}ms`);
}
}).observe({ type: 'first-input', buffered: true });
```
### User Timing API
```javascript
// Custom performance marks
performance.mark('before-render');
renderComponent();
performance.mark('after-render');
performance.measure('render-time', 'before-render', 'after-render');
const measures = performance.getEntriesByName('render-time');
console.log(`Render took: ${measures[0].duration}ms`);
// Clean up
performance.clearMarks();
performance.clearMeasures();
```
---
## 9. File APIs
### File System Access API
```javascript
// Open file
let fileHandle;
const [handle] = await window.showOpenFilePicker({
types: [{ description: 'Images', accept: { 'image/*': ['.png', '.jpg'] } }],
multiple: false,
});
fileHandle = handle;
// Read file
const file = await fileHandle.getFile();
const text = await file.text();
const arrayBuffer = await file.arrayBuffer();
// Write file (save)
const writable = await fileHandle.createWritable();
await writable.write('New content');
await writable.close();
// Show save dialog
const newHandle = await window.showSaveFilePicker({
suggestedName: 'document.txt',
types: [{ description: 'Text', accept: { 'text/plain': ['.txt'] } }],
});
```
### Drag and Drop
```javascript
const dropzone = document.getElementById('dropzone');
dropzone.addEventListener('dragover', (e) => {
e.preventDefault();
dropzone.classList.add('drag-over');
});
dropzone.addEventListener('dragleave', () => {
dropzone.classList.remove('drag-over');
});
dropzone.addEventListener('drop', async (e) => {
e.preventDefault();
dropzone.classList.remove('drag-over');
for (const file of e.dataTransfer.files) {
console.log('Dropped:', file.name, file.type, file.size);
}
});
// Data transfer
const draggable = document.querySelector('.draggable');
draggable.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/plain', draggable.dataset.id);
e.dataTransfer.effectAllowed = 'move';
});
```
---
## 10. Navigation API (Experimental)
```javascript
// Intercept navigations
navigator.navigation.addEventListener('navigate', (event) => {
console.log('Navigating to:', event.destination.url);
// Optionally handle with SPA routing
event.intercept({
handler: () => {
// Render new content
return Response.json({ content: '<h1>New Page</h1>' });
},
});
});
// Track current entry
const entry = navigator.navigation.currentEntry;
console.log('URL:', entry.key, entry.url);
```
---
## 11. Credentials API
```javascript
// Get saved credentials
const credentials = await navigator.credentials.get({ password: true });
if (credentials) {
console.log('User:', credentials.username, credentials.password);
}
// Store credentials
await navigator.credentials.store(new PasswordCredential({
id: 'alice@example.com',
password: 'secret123',
}));
// Prevent auto-fill
await navigator.credentials.preventSilentAccess();
```
---
## 12. Web Crypto API
```javascript
// Generate key pair
const keyPair = await crypto.subtle.generateKey(
{ name: 'RSA-OAEP', modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256' },
true, // extractable
['encrypt', 'decrypt']
);
// Encrypt
const encoded = new TextEncoder().encode('secret message');
const encrypted = await crypto.subtle.encrypt(
{ name: 'RSA-OAEP' },
keyPair.publicKey,
encoded
);
// Decrypt
const decrypted = await crypto.subtle.decrypt(
{ name: 'RSA-OAEP' },
keyPair.privateKey,
encrypted
);
console.log(new TextDecoder().decode(decrypted)); // "secret message"
// Derive key from password
const key = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode('password'),
'PBKDF2',
false,
['deriveBits', 'deriveKey']
);
const derivedKey = await crypto.subtle.deriveKey(
{ name: 'PBKDF2', salt: randomSalt, iterations: 100000, hash: 'SHA-256' },
key,
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
);
```
---
## Quick Reference: API Selection
| Need | API |
|------|-----|
| HTTP requests | Fetch API |
| Streaming data | Streams API |
| Offline support | Service Workers + Cache API |
| Real-time updates | WebSockets |
| Background processing | Web Workers |
| Persistent storage | IndexedDB |
| Simple storage | localStorage / sessionStorage |
| Camera/Microphone | MediaDevices |
| Record media | MediaRecorder |
| Audio processing | Web Audio API |
| Performance metrics | Performance Observer |
| Timing code | User Timing API |
| File read/write | File System Access API |
| Drag and drop | Drag & Drop API |
| Navigation control | Navigation API (experimental) |
| Credential management | Credentials API |
| Encryption | Web Crypto API |
| Notifications | Push API + Notification API |
| Geolocation | Geolocation API |
| Device orientation | DeviceOrientation API |
| Battery status | Battery Status API |
| Vibration | Vibration API |
| Clipboard | Clipboard API |
| Payment | Payment Request API |