Fetch API & Asynchronous HTTP
Asynchronous JavaScript and XML (AJAX) allows web applications to communicate with backend servers asynchronously without reloading the current page. Modern JavaScript applications utilize the native promise-based Fetch API for networking.
Evolution: XMLHttpRequest to fetch()
Before fetch(), asynchronous requests relied on XMLHttpRequest (XHR), which required verbose, callback-driven code:
const xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data");
xhr.onload = function () {
if (xhr.status >= 200 && xhr.status < 300) {
console.log(JSON.parse(xhr.responseText));
}
};
xhr.onerror = function () {
console.error("Network Error");
};
xhr.send();
The modern Fetch API streamlines networking with standard Promises and cleaner configuration objects:
// Modern Promise-based Fetch
fetch("https://api.example.com/data")
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.error("Fetch Error:", error));
Configuring Requests & Headers
The fetch() function accepts two arguments: the target resource URL and an optional options configuration object.
async function createUser(userData) {
const response = await fetch("https://api.example.com/users", {
method: "POST", // HTTP Method: GET, POST, PUT, DELETE, PATCH
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_JWT_TOKEN"
},
body: JSON.stringify(userData), // Serialize JavaScript object to JSON
mode: "cors", // cors, no-cors, or same-origin
cache: "no-cache"
});
return await response.json();
}
The Fetch Error-Handling Gotcha
Unlike HTTP libraries like Axios, fetch() does not reject its Promise on HTTP error status codes (such as 404 Not Found or 500 Internal Server Error). A fetch() Promise only rejects on network failures or blocked requests.
To handle HTTP errors properly, check the response.ok boolean property (status between 200–299):
async function safeFetch(url) {
try {
const response = await fetch(url);
// Check for HTTP error status codes (4xx, 5xx)
if (!response.ok) {
throw new Error(`HTTP Error! Status: ${response.status} ${response.statusText}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Request Failed:", error.message);
}
}
Aborting Requests & Timeouts (AbortController)
To cancel ongoing network requests or implement request timeouts, use the native AbortController interface:
async function fetchWithTimeout(resource, options = {}) {
const { timeout = 5000 } = options;
// 1. Create an AbortController instance
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(resource, {
...options,
signal: controller.signal // Connect signal to fetch
});
clearTimeout(id);
return await response.json();
} catch (error) {
if (error.name === "AbortError") {
throw new Error("Request timed out!");
}
throw error;
}
}
Interactive Playground: Live Fetch Explorer
Run the live fetch() request below using JSONPlaceholder API:
Best Practices
- Always Check
response.ok: Never assume a resolvedfetch()promise indicates a successful data payload. - Set Request Timeouts: Wrap network calls with
AbortControllerto prevent hanging pending requests on poor connections. - Use Typed Response Parsing: Match body parsing methods to payloads (
.json(),.text(),.blob(), or.arrayBuffer()).
Knowledge Check
Exercise Requirements:
Write a reusable function postJSON(url, payload) using async/await that sends a POST request, handles non-2xx HTTP errors properly, and returns the parsed JSON response.
async function postJSON(url, payload) {
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(`Failed to POST to ${url} - Status: ${response.status}`);
}
return await response.json();
}
Congratulations! You have completed Phase 04: Asynchronous JavaScript. Proceed to Phase 05: DOM & Browser APIs to build interactive Web applications!