Getting started
XMLHttpRequest (XHR)
const xhr = new XMLHttpRequest();
xhr.open("GET", "/api/data");
xhr.onload = function () {
console.log(xhr.responseText);
};
xhr.onerror = function () {
console.error("Request failed");
};
xhr.send();
Fetch API
// GET request
fetch("/api/data")
.then((response) => response.json())
.then((data) => console.log(data));
// POST request
fetch("/api/data", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ key: "value" }),
});
Async/await pattern
async function fetchData() {
try {
const response = await fetch("/api/data");
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
return data;
} catch (error) {
console.error(error.message);
}
}
Fetch API
Request options
fetch(url, {
method: "POST", // HTTP method
headers: {
// Request headers
"Content-Type": "application/json",
Authorization: "Bearer token",
},
body: JSON.stringify(data), // Request body
mode: "cors", // cors, no-cors, same-origin
credentials: "include", // omit, same-origin, include
cache: "no-cache", // default, no-cache, reload, force-cache
redirect: "follow", // follow, error, manual
});
Response methods
response.json(); // Parse JSON
response.text(); // Get as text
response.blob(); // Get as Blob
response.formData(); // Get as FormData
response.arrayBuffer(); // Get as ArrayBuffer
Response properties
response.ok; // true if 200-299
response.status; // HTTP status code
response.statusText; // Status message
response.headers; // Headers object
response.url; // Response URL
response.redirected; // true if redirected
Abort requests
const controller = new AbortController();
fetch(url, {
signal: controller.signal,
});
// Cancel request
controller.abort();
XMLHttpRequest
Basic XHR
const xhr = new XMLHttpRequest();
// Configure request
xhr.open("GET", "/api/data");
// Set headers
xhr.setRequestHeader("Content-Type", "application/json");
// Event handlers
xhr.onload = () => console.log(xhr.responseText);
xhr.onerror = () => console.error("Failed");
// Send request
xhr.send();
POST with JSON
const xhr = new XMLHttpRequest();
xhr.open("POST", "/api/data");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onload = function () {
if (xhr.status === 200) {
console.log(JSON.parse(xhr.responseText));
}
};
xhr.send(JSON.stringify({ key: "value" }));
ReadyState values
| Value | Constant | Description |
|---|---|---|
0 |
UNSENT |
open() not called |
1 |
OPENED |
open() called |
2 |
HEADERS_RECEIVED |
Headers available |
3 |
LOADING |
Downloading |
4 |
DONE |
Complete |
Event handlers
xhr.onreadystatechange = () => {}; // State changes
xhr.onload = () => {}; // Success
xhr.onerror = () => {}; // Network error
xhr.onprogress = (e) => {}; // Download progress
xhr.ontimeout = () => {}; // Timeout
xhr.onabort = () => {}; // Aborted
Timeout
xhr.timeout = 5000; // 5 seconds
xhr.ontimeout = () => {
console.error("Request timed out");
};
Progress tracking
// Upload progress
xhr.upload.onprogress = (e) => {
const percent = (e.loaded / e.total) * 100;
console.log(`Upload: ${percent}%`);
};
// Download progress
xhr.onprogress = (e) => {
const percent = (e.loaded / e.total) * 100;
console.log(`Download: ${percent}%`);
};
jQuery AJAX
$.ajax()
$.ajax({
url: "/api/data",
type: "POST", // GET, POST, PUT, DELETE
data: { key: "value" }, // Data to send
dataType: "json", // Expected response type
success: function (data) {
console.log(data);
},
error: function (jqXHR, textStatus, errorThrown) {
console.error(errorThrown);
},
complete: function () {
console.log("Request complete");
},
});
Promise style
$.ajax({ url: "/api/data" })
.done((data) => console.log(data))
.fail((error) => console.error(error))
.always(() => console.log("Complete"));
Shorthand methods
$.get(url, data, success, dataType);
$.post(url, data, success, dataType);
$.getJSON(url, data, success);
Global settings
$.ajaxSetup({
headers: { Authorization: "Bearer token" },
timeout: 5000,
});
Axios
Basic requests
// GET
axios.get(url).then((res) => console.log(res.data));
// POST
axios.post(url, data).then((res) => console.log(res.data));
// PUT
axios.put(url, data);
// DELETE
axios.delete(url);
Config object
axios({
method: "post",
url: "/api/data",
data: { key: "value" },
headers: { Authorization: "Bearer token" },
timeout: 5000,
responseType: "json",
});
Interceptors
// Request interceptor
axios.interceptors.request.use(
(config) => {
config.headers.Authorization = "Bearer token";
return config;
},
(error) => Promise.reject(error),
);
// Response interceptor
axios.interceptors.response.use(
(response) => response,
(error) => {
console.error(error.response.status);
return Promise.reject(error);
},
);
Create instance
const api = axios.create({
baseURL: "https://api.example.com",
timeout: 5000,
headers: { "X-Custom-Header": "value" },
});
api.get("/users"); // https://api.example.com/users
Error handling
axios.get(url).catch((error) => {
if (error.response) {
// Server responded with error
console.log(error.response.status);
console.log(error.response.data);
} else if (error.request) {
// No response received
console.log(error.request);
} else {
// Setup error
console.log(error.message);
}
});
// Check if Axios error
if (axios.isAxiosError(error)) {
// Handle Axios-specific error
}
Common patterns
Sending JSON
// Fetch
fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
// XHR
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send(JSON.stringify(data));
// Axios (automatic)
axios.post(url, data);
// jQuery
$.ajax({
url,
type: "POST",
contentType: "application/json",
data: JSON.stringify(data),
});
FormData / File upload
const formData = new FormData();
formData.append("file", fileInput.files[0]);
formData.append("name", "filename");
// Fetch (no Content-Type!)
fetch(url, {
method: "POST",
body: formData,
});
// Axios
axios.post(url, formData);
// jQuery
$.ajax({
url,
type: "POST",
data: formData,
processData: false,
contentType: false,
});
⚠️ Don't set Content-Type for FormData — browser sets boundary
Authorization headers
// Fetch
fetch(url, {
headers: { Authorization: "Bearer token" },
});
// XHR
xhr.setRequestHeader("Authorization", "Bearer token");
// Axios (default)
axios.defaults.headers.common["Authorization"] = "Bearer token";
// jQuery (global)
$.ajaxSetup({
headers: { Authorization: "Bearer token" },
});
Timeout handling
// Fetch (AbortController)
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);
fetch(url, { signal: controller.signal });
// XHR
xhr.timeout = 5000;
xhr.ontimeout = () => console.error("Timeout");
// Axios
axios.get(url, { timeout: 5000 });
// jQuery
$.ajax({ url, timeout: 5000 });
CORS with credentials
// Fetch
fetch(url, { credentials: "include" });
// XHR
xhr.withCredentials = true;
// Axios
axios.get(url, { withCredentials: true });
// jQuery
$.ajax({
url,
xhrFields: { withCredentials: true },
});
Parallel requests
// Promise.all with fetch
Promise.all([fetch("/api/users"), fetch("/api/posts"), fetch("/api/comments")])
.then((responses) => Promise.all(responses.map((r) => r.json())))
.then(([users, posts, comments]) => {
console.log({ users, posts, comments });
});
// Axios with Promise.all
Promise.all([axios.get("/api/users"), axios.get("/api/posts")]).then(
([usersRes, postsRes]) => {
console.log(usersRes.data, postsRes.data);
},
);
Advanced techniques
Streaming responses
const response = await fetch(url);
const reader = response.body.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = new TextDecoder().decode(value);
console.log(chunk);
}
Progress tracking
// Axios with progress
axios.post(url, data, {
onUploadProgress: (progressEvent) => {
const percent = Math.round(
(progressEvent.loaded * 100) / progressEvent.total,
);
console.log(`Upload: ${percent}%`);
},
onDownloadProgress: (progressEvent) => {
const percent = Math.round(
(progressEvent.loaded * 100) / progressEvent.total,
);
console.log(`Download: ${percent}%`);
},
});
Retry logic
async function fetchWithRetry(url, options = {}, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url, options);
if (response.ok) return response;
} catch (error) {
if (i === retries - 1) throw error;
await new Promise((r) => setTimeout(r, 1000 * (i + 1)));
}
}
}
Request caching
const cache = new Map();
async function cachedFetch(url) {
if (cache.has(url)) {
return cache.get(url);
}
const response = await fetch(url);
const data = await response.json();
cache.set(url, data);
return data;
}
CORS
Client-side configuration
fetch(url, {
mode: "cors", // cors, no-cors, same-origin
credentials: "include", // include cookies
});
Required server headers
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
Preflight requests
// These trigger preflight OPTIONS
fetch(url, {
method: "PUT", // Non-simple method
headers: {
"X-Custom-Header": "value", // Custom header
},
});
⚠️ Custom headers/non-simple methods trigger preflight OPTIONS
HTTP status codes
Success codes
| Code | Meaning |
|---|---|
200 |
OK |
201 |
Created |
204 |
No Content |
Redirect codes
| Code | Meaning |
|---|---|
301 |
Moved Permanently |
302 |
Found (temporary) |
304 |
Not Modified |
Client error codes
| Code | Meaning |
|---|---|
400 |
Bad Request |
401 |
Unauthorized |
403 |
Forbidden |
404 |
Not Found |
429 |
Too Many Requests |
Server error codes
| Code | Meaning |
|---|---|
500 |
Internal Server Error |
502 |
Bad Gateway |
503 |
Service Unavailable |
Error handling
Fetch errors
fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
})
.catch((error) => {
if (error.name === "AbortError") {
console.log("Request aborted");
} else if (error.name === "TypeError") {
console.log("Network error");
} else {
console.error(error.message);
}
});
⚠️ fetch() doesn't reject on 404/500 — must check response.ok
Axios errors
axios.get(url).catch((error) => {
if (error.response) {
// Server error (4xx, 5xx)
console.error(error.response.status);
console.error(error.response.data);
} else if (error.request) {
// No response received
console.error("No response");
} else {
// Request setup error
console.error(error.message);
}
});
jQuery errors
$.ajax({ url }).fail((jqXHR, textStatus, errorThrown) => {
console.error(`${textStatus}: ${errorThrown}`);
console.error(jqXHR.responseText);
});
⚠️ jQuery rejects on HTTP errors, fetch doesn't
Gotchas
Common pitfalls
⚠️ fetch() doesn't reject on HTTP errors — Always check response.ok or response.status
⚠️ Don't set Content-Type for FormData — Browser automatically sets it with boundary parameter
⚠️ CORS preflight — Custom headers and non-simple methods trigger OPTIONS preflight
⚠️ Axios auto-transforms JSON — Sets Content-Type and parses responses automatically
⚠️ fetch() vs jQuery — jQuery rejects on HTTP errors, fetch only rejects on network failures
⚠️ Credentials not sent by default — Use credentials: 'include' or withCredentials: true
⚠️ Timeout in fetch — No built-in timeout, must use AbortController with setTimeout
⚠️ Progress events — Not available in fetch() for upload, use XHR or Axios
Also see
- Fetch API (developer.mozilla.org)
- XMLHttpRequest (developer.mozilla.org)
- Axios documentation (axios-http.com)
- jQuery.ajax() (api.jquery.com)