HTTP Requests & Error Handling in Javascript
Historical Context - In the late 1990s, web apps became more interactive, and developers needed a way to fetch data asynchronously - this led to XMLHttpRequest (XHR). The Fetch API (2015) later modernized HTTP requests using promises.
JavaScript provides several methods to make HTTP requests to fetch or send data to servers.
XMLHttpRequest (XHR)
Legacy API for making HTTP requests. Handles responses via event listeners. While it offers fine-grained control over the request lifecycle, it does not have native promise support.
function fetchData() {
const xhr = new XMLHttpRequest();
xhr.open("GET", "https://jsonplaceholder.typicode.com/users", true); // true for async
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) { // readyState === 4 means the request is complete.
const data = JSON.parse(xhr.responseText);
console.log(data.map(user => user.name));
} else if (xhr.readyState === 4) {
console.error("Error:", xhr.status);
}
};
xhr.send();
}
fetchData();
// Output: Array of user names or error if request fails
Fetch API
Modern, promise-based alternative to XHR, built into browsers. Returns a Promise resolving to a Response object. fetch only rejects on network errors. HTTP errors (like 404 or 500) do not reject the promise, we must check response.ok manually.
async function fetchData() {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
if (!response.ok) throw new Error(`HTTP error: ${response.status}`);
const data = await response.json();
console.log(data.map(user => user.name));
} catch (error) {
console.error("Error:", error);
}
}
fetchData();
// Output: Array of user names or error if request fails
Axios
Third-party library for HTTP requests, built on top of XHR (in browsers) or Node.js HTTP module. It’s promise-based and provides additional features. Unlike fetch, Axios automatically rejects the promise for HTTP error status codes.
import axios from "axios"; // Requires module bundler or CDN
async function fetchData() {
try {
const response = await axios.get("https://jsonplaceholder.typicode.com/users");
console.log(response.data.map(user => user.name));
} catch (error) {
console.error("Error:", error.message);
}
}
fetchData();
// Output: Array of user names or error if request fails
Error Handling in Asynchronous JS
Execution is deferred (via event loop). Errors in asynchronous code do not propagate like synchronous errors, so they must be handled explicitly.
Example with Callbacks:
Here, the error is caught inside the async function and passed back to the caller via the callback.(Nodejs uses this convention)
function fetchData(callback){
setTimeout(()=>{
try{
throw new Error("Failed to fetch data");
} catch(error){
callback(error,null);
}
},1000);
}
fetchData((error, data)=>{
if(error) {
console.error("Error:", error.message);
return;
}
console.log("Data:", data);
});
Example with Promises:
Promises provide a cleaner way: instead of manually passing errors, you call reject(). Consumers handle errors using .catch().
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error("Failed to fetch data"));
}, 1000);
});
}
fetchData()
.then((data) => console.log("Data:", data))
.catch((error) => console.error("Error:", error.message));
Example with async/await:
async/await syntax makes asynchronous code look synchronous. Errors inside async functions automatically become rejected promises. You handle them with try…catch around the await.
async function fetchData() {
throw new Error("Failed to fetch data");
}
(async () => {
try {
const data = await fetchData();
console.log("Data:", data);
} catch (error) {
console.error("Error:", error.message);
}
})();