Skip to content

AJAX and Fetch - Interacting with Web Servers

Modern web applications heavily rely on network communication to fetch or send data to servers dynamically. This is where AJAX (Asynchronous JavaScript and XML) and its evolution with the modern fetch API come into play. In this blog, we’ll cover the following:

  1. What AJAX is and when to use it.
  2. Basics of XMLHttpRequest (the legacy method).
  3. Using the modern fetch() API.
  4. How to handle JSON, errors, and chaining requests.

AJAX, short for Asynchronous JavaScript and XML, is a technique that allows web pages to communicate with a server without reloading the page. It enables the creation of dynamic, responsive web experiences by handling asynchronous requests to fetch or send data.

You should use AJAX (and similar modern techniques like fetch) when:

  • You need to retrieve or send data to a server dynamically (e.g., for live updates or form submissions).
  • You want to interact with APIs (Application Programming Interfaces).
  • You want to enhance user experience by avoiding full-page reloads.

Example use cases:

  • Loading additional content when users scroll.
  • Submitting a form and displaying results without leaving the page.
  • Fetching live data, like weather or stock prices.

The XMLHttpRequest (XHR) object was the original way to implement AJAX. Although outdated today, it’s still useful to understand its basics for legacy code or compatibility purposes.

Here’s an example of how to use XMLHttpRequest to fetch data:

const xhr = new XMLHttpRequest();
// Open a new GET request
xhr.open(
"GET",
"[https://jsonplaceholder.typicode.com/posts](https://jsonplaceholder.typicode.com/posts)",
);
// Set up a callback for when the response is ready
xhr.onload = () => {
if (xhr.status === 200) {
const data = JSON.parse(xhr.responseText); // Parse JSON response
console.log(data);
} else {
console.error("Request failed:", xhr.statusText);
}
};
// Send the request
xhr.send();

However, XMLHttpRequest comes with some limitations, such as a verbose syntax and lack of support for promises. This is where the modern fetch() API shines.


The fetch() API is the modern, promise-based way to make HTTP requests. It offers a simpler, more flexible syntax compared to XMLHttpRequest and is well-suited for modern JavaScript workflows.

fetch(
"[https://jsonplaceholder.typicode.com/posts](https://jsonplaceholder.typicode.com/posts)",
)
.then((response) => {
if (!response.ok) {
throw new Error("Network response was not ok!");
}
return response.json(); // Parse JSON response
})
.then((data) => {
console.log(data); // Handle the fetched data
})
.catch((error) => {
console.error("Fetch error:", error);
});
  • Promise-based: Makes chaining and error handling cleaner and more intuitive.
  • Supports modern async/await: Allows for a synchronous-like code flow.

4. Parsing JSON, Error Handling, and Chaining Requests

Section titled “4. Parsing JSON, Error Handling, and Chaining Requests”

Most web APIs respond with data in JSON format. Both fetch and XMLHttpRequest require you to explicitly parse the JSON.

Example:

fetch(
"[https://jsonplaceholder.typicode.com/posts](https://jsonplaceholder.typicode.com/posts)",
)
.then((response) => response.json()) // Parse JSON directly
.then((data) => console.log(data));

Error handling in fetch() is done using .catch() for network errors. However, since fetch() resolves on any response (even 404s), you should also check the response status.

Example:

fetch(
"[https://jsonplaceholder.typicode.com/invalid-endpoint](https://jsonplaceholder.typicode.com/invalid-endpoint)",
)
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then((data) => console.log(data))
.catch((error) => console.error("Fetch error:", error));

Sometimes you may need to make multiple requests in sequence. This is easy to do with promise chaining or async/await.

Example:

fetch(
"[https://jsonplaceholder.typicode.com/posts/1](https://jsonplaceholder.typicode.com/posts/1)",
)
.then((response) => response.json())
.then((post) => {
console.log(`Post title: ${post.title}`); // Fetch comments for the post
return fetch(`https://jsonplaceholder.typicode.com/posts/1/comments`);
})
.then((response) => response.json())
.then((comments) => console.log("Comments:", comments))
.catch((error) => console.error("Error:", error));

Or using async/await:

async function fetchPostAndComments() {
try {
const postResponse = await fetch(
"[https://jsonplaceholder.typicode.com/posts/1](https://jsonplaceholder.typicode.com/posts/1)",
);
if (!postResponse.ok) throw new Error("Failed to fetch post!");
const post = await postResponse.json();
console.log(`Post title: ${post.title}`);
const commentsResponse = await fetch(
"https://jsonplaceholder.typicode.com/posts/1/comments",
);
if (!commentsResponse.ok) throw new Error("Failed to fetch comments!");
const comments = await commentsResponse.json();
console.log("Comments:", comments);
} catch (error) {
console.error("Error:", error);
}
}
fetchPostAndComments();