Fetch API and Network Requests
1. Fetch API (fetch, Request, Response, Headers)
| Method/Property | Syntax | Description | Browser Support |
|---|---|---|---|
| fetch | fetch(url, options) |
Makes HTTP request, returns Promise<Response>. Modern replacement for XMLHttpRequest. | Modern Browsers |
| Request Constructor | new Request(url, options) |
Creates Request object that can be reused. Options include method, headers, body, mode, credentials. | Modern Browsers |
| Response Constructor | new Response(body, options) |
Creates Response object. Options include status, statusText, headers. Used in Service Workers. | Modern Browsers |
| Headers Constructor | new Headers(init) |
Creates Headers object for request/response headers. Methods: get, set, append, delete, has. | Modern Browsers |
| Fetch Option | Type | Description | Default |
|---|---|---|---|
| method | string |
HTTP method: GET, POST, PUT, DELETE, PATCH, etc. | "GET" |
| headers | Headers | object |
Request headers as Headers object or plain object | {} |
| body | string | FormData | Blob |
Request body. Not allowed for GET/HEAD. Auto-sets Content-Type. | undefined |
| mode | string |
Request mode: "cors", "no-cors", "same-origin" |
"cors" |
| credentials | string |
Cookie handling: "omit", "same-origin", "include" |
"same-origin" |
| cache | string |
Cache mode: "default", "no-store", "reload", "no-cache", "force-cache" |
"default" |
| redirect | string |
Redirect mode: "follow", "error", "manual" |
"follow" |
| referrer | string |
Referrer URL or "no-referrer" |
Document URL |
| integrity | string |
Subresource Integrity hash for verification | "" |
| signal | AbortSignal |
AbortSignal to cancel request | undefined |
| Response Property | Type | Description |
|---|---|---|
| ok | boolean |
True if status is 200-299. Convenient for checking success. |
| status | number |
HTTP status code (200, 404, 500, etc.) |
| statusText | string |
HTTP status message ("OK", "Not Found", etc.) |
| headers | Headers |
Response headers as Headers object |
| url | string |
Final URL after redirects |
| redirected | boolean |
True if response is result of redirect |
| type | string |
Response type: "basic", "cors", "error", "opaque" |
| bodyUsed | boolean |
True if body has been read. Body can only be read once. |
| Response Body Method | Returns | Description |
|---|---|---|
| json() | Promise<any> |
Parses response body as JSON |
| text() | Promise<string> |
Reads response body as text |
| blob() | Promise<Blob> |
Reads response body as Blob (files, images) |
| arrayBuffer() | Promise<ArrayBuffer> |
Reads response body as binary data |
| formData() | Promise<FormData> |
Parses response body as FormData |
Example: Basic fetch usage
// Simple GET request
fetch("/api/users")
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then((data) => {
console.log("Users:", data);
})
.catch((error) => {
console.error("Fetch error:", error);
});
// Async/await pattern (preferred)
async function fetchUsers() {
try {
const response = await fetch("/api/users");
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Error:", error);
throw error;
}
}
// POST request with JSON
async function createUser(userData) {
const response = await fetch("/api/users", {
"method": "POST",
"headers": {
"Content-Type": "application/json"
},
"body": JSON.stringify(userData)
});
return response.json();
}
Example: Headers and credentials
// Using Headers object
const headers = new Headers();
headers.append("Authorization", "Bearer token123");
headers.append("Content-Type", "application/json");
const response = await fetch("/api/protected", {
"method": "GET",
"headers": headers,
"credentials": "include" // Send cookies
});
// Check response headers
console.log("Content-Type:", response.headers.get("content-type"));
console.log("Has header:", response.headers.has("x-custom"));
// Iterate headers
for (let [key, value] of response.headers) {
console.log(`${key}: ${value}`);
}
// FormData for file upload
const formData = new FormData();
formData.append("file", fileInput.files[0]);
formData.append("description", "Profile picture");
await fetch("/api/upload", {
"method": "POST",
"body": formData // Content-Type auto-set to multipart/form-data
});
Note: Fetch doesn't reject on HTTP errors (404, 500) - check
response.ok or response.status. Body can only be read once - use response.clone() if you need to read it multiple times. Default credentials: "same-origin" doesn't send cookies to different origins.
2. Fetch Advanced (AbortController, ReadableStream)
| Feature | Syntax | Description | Use Case |
|---|---|---|---|
| AbortController | new AbortController() |
Creates controller for aborting fetch requests. Has signal property and abort() method. |
Cancel requests |
| signal | controller.signal |
AbortSignal to pass to fetch. Automatically aborts when controller.abort() called. | Link abort to fetch |
| abort() | controller.abort(reason) |
Aborts associated request. Optional reason passed to error handler. | Trigger cancellation |
| ReadableStream | response.body |
Response body as ReadableStream. Allows progressive reading of large responses. | Stream large data |
| getReader() | stream.getReader() |
Gets ReadableStreamDefaultReader to read chunks. Locks stream. | Read stream chunks |
| read() | reader.read() |
Returns Promise<{value, done}>. Value is Uint8Array chunk. | Read next chunk |
Example: Aborting fetch requests
// Basic abort pattern
const controller = new AbortController();
const signal = controller.signal;
fetch("/api/data", { signal })
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => {
if (error.name === "AbortError") {
console.log("Fetch aborted");
} else {
console.error("Fetch error:", error);
}
});
// Abort after 5 seconds
setTimeout(() => controller.abort(), 5000);
// Abort on user action
cancelButton.addEventListener("click", () => {
controller.abort("User cancelled");
});
// Timeout utility
function fetchWithTimeout(url, options = {}, timeout = 5000) {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
return fetch(url, {
...options,
"signal": controller.signal
}).finally(() => clearTimeout(id));
}
// Usage
try {
const response = await fetchWithTimeout("/api/slow", {}, 3000);
const data = await response.json();
} catch (error) {
if (error.name === "AbortError") {
console.error("Request timed out");
}
}
Example: Streaming response data
// Stream large file download with progress
async function downloadWithProgress(url) {
const response = await fetch(url);
const contentLength = response.headers.get("content-length");
const total = parseInt(contentLength, 10);
let loaded = 0;
const reader = response.body.getReader();
const chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
loaded += value.length;
const progress = (loaded / total) * 100;
console.log(`Progress: ${progress.toFixed(2)}%`);
}
// Concatenate chunks into single Uint8Array
const chunksAll = new Uint8Array(loaded);
let position = 0;
for (let chunk of chunks) {
chunksAll.set(chunk, position);
position += chunk.length;
}
return chunksAll;
}
// Stream text data line by line
async function streamText(url) {
const response = await fetch(url);
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value, { "stream": true });
console.log("Chunk:", text);
}
}
Example: Advanced patterns
// Request deduplication
const requestCache = new Map();
async function fetchOnce(url) {
if (requestCache.has(url)) {
return requestCache.get(url);
}
const promise = fetch(url).then((r) => r.json());
requestCache.set(url, promise);
try {
return await promise;
} catch (error) {
requestCache.delete(url); // Remove failed request
throw error;
}
}
// Retry with exponential backoff
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url, options);
if (!response.ok && i < maxRetries - 1) {
throw new Error(`HTTP ${response.status}`);
}
return response;
} catch (error) {
if (i === maxRetries - 1) throw error;
const delay = Math.pow(2, i) * 1000; // 1s, 2s, 4s
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
}
// Parallel requests with Promise.all
async function fetchMultiple(urls) {
const promises = urls.map((url) =>
fetch(url).then((r) => r.json())
);
return Promise.all(promises);
}
// Race condition - first to respond wins
async function fetchFastest(urls) {
const promises = urls.map((url) =>
fetch(url).then((r) => r.json())
);
return Promise.race(promises);
}
Warning:
AbortController.abort() throws an error in the fetch promise - always catch it. Streams can only be read once - lock is not released until reader is closed. When streaming, make sure to handle errors and cleanup properly to avoid memory leaks.
3. XMLHttpRequest and AJAX
| Property/Method | Description | Modern Alternative |
|---|---|---|
| new XMLHttpRequest() | Creates XHR object for AJAX requests | Use fetch() |
| open(method, url, async) | Initializes request. Third parameter is async flag (default true). | fetch(url, {method}) |
| send(body) | Sends request. Body for POST/PUT. | fetch(url, {body}) |
| abort() | Aborts request in progress | AbortController |
| setRequestHeader(name, value) | Sets HTTP request header. Call after open(), before send(). | fetch(url, {headers}) |
| getResponseHeader(name) | Gets response header value | response.headers.get() |
| readyState | Request state: 0=UNSENT, 1=OPENED, 2=HEADERS_RECEIVED, 3=LOADING, 4=DONE | Promise states |
| status | HTTP status code (200, 404, etc.) | response.status |
| statusText | HTTP status message | response.statusText |
| responseText | Response body as text | response.text() |
| responseXML | Response body as XML Document | Parse with DOMParser |
| response | Response body (type depends on responseType) | Body reading methods |
| responseType | Expected response type: "", "text", "json", "blob", "arraybuffer", "document" | Body reading methods |
| timeout | Timeout in milliseconds. 0 means no timeout. | AbortSignal.timeout() |
| withCredentials | Include cookies in cross-origin requests | credentials: "include" |
| Event | Description | When Fired |
|---|---|---|
| loadstart | Request started | When request begins |
| progress | Data transfer in progress | Periodically while loading |
| load | Request completed successfully | When response received |
| error | Request failed | Network error occurred |
| abort | Request was aborted | When abort() called |
| timeout | Request timed out | Exceeded timeout duration |
| loadend | Request finished (success or failure) | After load/error/abort |
Example: XMLHttpRequest basic usage (legacy)
// GET request
const xhr = new XMLHttpRequest();
xhr.open("GET", "/api/users", true);
xhr.onload = function() {
if (xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
console.log("Success:", data);
} else {
console.error("Error:", xhr.status);
}
};
xhr.onerror = function() {
console.error("Network error");
};
xhr.send();
// POST request with JSON
const xhr2 = new XMLHttpRequest();
xhr2.open("POST", "/api/users", true);
xhr2.setRequestHeader("Content-Type", "application/json");
xhr2.onreadystatechange = function() {
if (xhr2.readyState === 4) { // DONE
if (xhr2.status === 201) {
console.log("Created:", JSON.parse(xhr2.responseText));
}
}
};
xhr2.send(JSON.stringify({ "name": "John" }));
// Upload with progress
const formData = new FormData();
formData.append("file", file);
const xhr3 = new XMLHttpRequest();
xhr3.upload.addEventListener("progress", (event) => {
if (event.lengthComputable) {
const percent = (event.loaded / event.total) * 100;
console.log(`Upload: ${percent.toFixed(2)}%`);
}
});
xhr3.open("POST", "/api/upload", true);
xhr3.send(formData);
Note: XMLHttpRequest is legacy API - use Fetch API for new projects. XHR still useful for upload progress tracking (Fetch doesn't support this natively yet). XHR requires more boilerplate code and callback-based error handling compared to Promise-based Fetch.
4. WebSocket API (new WebSocket, send, onmessage)
| Feature | Syntax | Description | Browser Support |
|---|---|---|---|
| WebSocket Constructor | new WebSocket(url, protocols) |
Creates WebSocket connection. URL must start with ws:// or wss://. Optional protocols array. |
All Browsers |
| send(data) | ws.send(data) |
Sends data to server. Accepts string, Blob, ArrayBuffer, ArrayBufferView. | All Browsers |
| close(code, reason) | ws.close(code, reason) |
Closes connection. Optional close code and reason string. | All Browsers |
| Property | Type | Description | Values |
|---|---|---|---|
| readyState | number |
Connection state | 0=CONNECTING, 1=OPEN, 2=CLOSING, 3=CLOSED |
| url | string |
WebSocket URL | Read-only |
| protocol | string |
Negotiated subprotocol | Read-only |
| bufferedAmount | number |
Bytes queued but not yet transmitted | Read-only |
| binaryType | string |
Binary data type received | "blob" or "arraybuffer" |
| Event | Description | Event Object Properties |
|---|---|---|
| open | Connection established successfully | Standard Event object |
| message | Message received from server | data: message content (string, Blob, ArrayBuffer) |
| error | Error occurred | Standard Event object |
| close | Connection closed | code: close code, reason: close reason, wasClean: boolean |
Example: WebSocket basic usage
// Create WebSocket connection
const ws = new WebSocket("wss://example.com/socket");
// Connection opened
ws.addEventListener("open", (event) => {
console.log("Connected to server");
ws.send("Hello Server!");
});
// Listen for messages
ws.addEventListener("message", (event) => {
console.log("Message from server:", event.data);
// Handle different data types
if (typeof event.data === "string") {
const message = JSON.parse(event.data);
handleMessage(message);
} else if (event.data instanceof Blob) {
// Handle binary data
event.data.text().then((text) => console.log(text));
}
});
// Handle errors
ws.addEventListener("error", (event) => {
console.error("WebSocket error:", event);
});
// Connection closed
ws.addEventListener("close", (event) => {
console.log("Connection closed:", event.code, event.reason);
if (event.wasClean) {
console.log("Clean close");
} else {
console.log("Connection died");
}
});
// Send different data types
ws.send("Text message");
ws.send(JSON.stringify({ "type": "chat", "message": "Hello" }));
ws.send(new Blob(["binary data"]));
ws.send(new Uint8Array([1, 2, 3, 4]));
// Close connection gracefully
ws.close(1000, "Normal closure");
Example: WebSocket reconnection pattern
class WebSocketClient {
constructor(url) {
this.url = url;
this.reconnectInterval = 1000;
this.maxReconnectInterval = 30000;
this.reconnectDecay = 1.5;
this.connect();
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
console.log("WebSocket connected");
this.reconnectInterval = 1000; // Reset on successful connection
};
this.ws.onmessage = (event) => {
this.handleMessage(JSON.parse(event.data));
};
this.ws.onerror = (error) => {
console.error("WebSocket error:", error);
};
this.ws.onclose = (event) => {
console.log("WebSocket closed:", event.code);
this.reconnect();
};
}
reconnect() {
console.log(`Reconnecting in ${this.reconnectInterval}ms...`);
setTimeout(() => {
this.connect();
this.reconnectInterval = Math.min(
this.reconnectInterval * this.reconnectDecay,
this.maxReconnectInterval
);
}, this.reconnectInterval);
}
send(data) {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(data));
} else {
console.warn("WebSocket not ready");
}
}
handleMessage(data) {
console.log("Received:", data);
}
close() {
this.ws.close(1000, "Client closing");
}
}
// Usage
const client = new WebSocketClient("wss://example.com/socket");
client.send({ "type": "subscribe", "channel": "updates" });
Note: Always use
wss:// (secure WebSocket) in production, not ws://. Check readyState before calling send() to avoid errors. Implement reconnection logic for production applications. Use bufferedAmount to check if send buffer is full.
5. Server-Sent Events (EventSource, onmessage)
| Feature | Syntax | Description | Browser Support |
|---|---|---|---|
| EventSource Constructor | new EventSource(url, options) |
Creates server-sent events connection. One-way: server to client only. | Modern Browsers |
| close() | eventSource.close() |
Closes connection. Cannot be reopened. | Modern Browsers |
| Property | Type | Description | Values |
|---|---|---|---|
| readyState | number |
Connection state | 0=CONNECTING, 1=OPEN, 2=CLOSED |
| url | string |
Source URL | Read-only |
| withCredentials | boolean |
Whether to send credentials (cookies) | Set in constructor options |
| Event | Description | When Fired |
|---|---|---|
| open | Connection opened | When connection established |
| message | Message received from server | Server sends data without event name |
| error | Error occurred or connection lost | Network error or server closed connection |
| Custom events | Named events from server | Server sends data with event: field |
Example: EventSource basic usage
// Create EventSource connection
const eventSource = new EventSource("/api/events");
// Connection opened
eventSource.addEventListener("open", () => {
console.log("SSE connection opened");
});
// Receive messages
eventSource.addEventListener("message", (event) => {
console.log("Message:", event.data);
const data = JSON.parse(event.data);
updateUI(data);
});
// Custom named events from server
eventSource.addEventListener("notification", (event) => {
const notification = JSON.parse(event.data);
showNotification(notification);
});
eventSource.addEventListener("update", (event) => {
const update = JSON.parse(event.data);
console.log("Update received:", update);
});
// Handle errors
eventSource.addEventListener("error", (event) => {
console.error("SSE error");
if (eventSource.readyState === EventSource.CLOSED) {
console.log("Connection closed");
}
});
// Close connection when done
// eventSource.close();
Example: Server-side SSE format (Node.js)
// Server sends events in this format:
// event: eventName
// data: JSON data
// id: unique id (optional)
// retry: milliseconds (optional)
// (blank line)
// Node.js server example
app.get("/api/events", (req, res) => {
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");
// Send message without event name (uses 'message' event)
res.write("data: " + JSON.stringify({ "message": "Hello" }) + "\n\n");
// Send named event
res.write("event: notification\n");
res.write("data: " + JSON.stringify({ "title": "New message" }) + "\n");
res.write("id: 123\n");
res.write("\n");
// Send periodic updates
const interval = setInterval(() => {
res.write(`event: update\n`);
res.write(`data: ${JSON.stringify({ "time": Date.now() })}\n\n`);
}, 1000);
req.on("close", () => {
clearInterval(interval);
res.end();
});
});
Note: EventSource automatically reconnects on connection loss (WebSocket doesn't). SSE is one-way only (server to client) - use WebSocket for bidirectional communication. SSE uses regular HTTP, works through most proxies and firewalls. Default retry timeout is 3 seconds.
6. Navigator.sendBeacon for Analytics
| Feature | Syntax | Description | Browser Support |
|---|---|---|---|
| sendBeacon | navigator.sendBeacon(url, data) |
Sends asynchronous POST request that completes even if page is unloading. Returns boolean indicating if queued successfully. | Modern Browsers |
| Data Type | Description | Content-Type |
|---|---|---|
| ArrayBuffer | Binary data | No Content-Type set |
| Blob | Binary data with type | Uses Blob's type property |
| FormData | Form data | multipart/form-data |
| URLSearchParams | URL-encoded data | application/x-www-form-urlencoded |
| String | Text data | text/plain |
Example: sendBeacon usage patterns
// Track page unload
window.addEventListener("beforeunload", () => {
const data = JSON.stringify({
"event": "page_exit",
"timestamp": Date.now(),
"duration": performance.now()
});
const blob = new Blob([data], { "type": "application/json" });
const queued = navigator.sendBeacon("/api/analytics", blob);
if (!queued) {
console.warn("Beacon not queued - data limit exceeded");
}
});
// Track user actions
function trackEvent(eventName, properties) {
const data = new URLSearchParams({
"event": eventName,
"timestamp": Date.now(),
...properties
});
navigator.sendBeacon("/api/track", data);
}
// Usage
trackEvent("button_click", { "button": "submit", "page": "/checkout" });
// Track errors
window.addEventListener("error", (event) => {
const errorData = new FormData();
errorData.append("message", event.message);
errorData.append("filename", event.filename);
errorData.append("lineno", event.lineno);
errorData.append("colno", event.colno);
navigator.sendBeacon("/api/errors", errorData);
});
// Track visibility changes
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
const sessionData = JSON.stringify({
"sessionId": sessionStorage.getItem("sessionId"),
"duration": performance.now(),
"interactionCount": getInteractionCount()
});
navigator.sendBeacon(
"/api/session-end",
new Blob([sessionData], { "type": "application/json" })
);
}
});
Note:
sendBeacon is designed for analytics and diagnostics that must not be lost when page unloads. Request completes even if user closes tab/window. Cannot read response or handle errors. Has size limits (typically 64KB). Uses POST method only.
Warning: Don't use sendBeacon for critical data that requires confirmation - there's no way to know if request succeeded. Browsers may limit total beacon data size. Use Blob with proper Content-Type for JSON data, not plain string.
Network Communication Best Practices
- Use Fetch API for modern HTTP requests - cleaner syntax and Promise-based
- Always check
response.okorresponse.status- fetch doesn't reject on HTTP errors - Use
AbortControllerfor request cancellation and timeouts - Implement proper error handling with try/catch for async/await patterns
- Use WebSocket for bidirectional real-time communication (chat, live updates)
- Use EventSource for one-way server-to-client streaming (notifications, live feeds)
- Implement reconnection logic for WebSocket connections in production
- Use
credentials: "include"to send cookies with cross-origin requests - Use
navigator.sendBeaconfor analytics during page unload - guaranteed delivery - Stream large responses with ReadableStream for memory efficiency and progress tracking