4.8: Fetch API & AJAX
Understand asynchronous JavaScript, promises, and async/await for handling time-based operations and API calls. Learn how to fetch data from external sources and work with JSON to create dynamic, data-driven web applications.
1. What is AJAX?
AJAX (Asynchronous JavaScript And XML) allows updating parts of a web page without reloading the entire page.
Traditional Web (Without AJAX)
1. User clicks link
2. Browser sends request to server
3. Server sends back FULL HTML page
4. Browser reloads entire page
5. User sees new page
Problem: Slow, poor user experience, wastes bandwidth.
Modern Web (With AJAX)
1. User interacts with page
2. JavaScript sends request to server
3. Server sends back DATA (JSON)
4. JavaScript updates page dynamically
5. No page reload!
Examples:
- Google Maps (pan without reload)
- Gmail (check emails without refresh)
- Facebook feed (infinite scroll)
- Search autocomplete
2. The Fetch API
Fetch is the modern API for making HTTP requests (replaces old XMLHttpRequest).
Basic GET Request
fetch("https://jsonplaceholder.typicode.com/users/1")
.then(response => response.json()) // Parse JSON
.then(data => {
console.log(data);
})
.catch(error => {
console.log("Error:", error);
});
With async/await (cleaner):
async function getUser() {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/users/1");
const data = await response.json();
console.log(data);
} catch (error) {
console.log("Error:", error);
}
}
getUser();
Python comparison (requests library):
import requests
response = requests.get("https://jsonplaceholder.typicode.com/users/1")
data = response.json()
print(data)
Response Object
const response = await fetch(url);
console.log(response.status); // 200, 404, 500, etc.
console.log(response.ok); // true if 200-299
console.log(response.statusText); // "OK", "Not Found", etc.
console.log(response.headers); // Response headers
console.log(response.url); // Final URL (after redirects)
// Parse response body
const json = await response.json(); // JSON
const text = await response.text(); // Plain text
const blob = await response.blob(); // Binary (images, files)
Checking Response Status
async function fetchUser(id) {
const response = await fetch(`https://api.example.com/users/${id}`);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return await response.json();
}
try {
const user = await fetchUser(1);
console.log(user);
} catch (error) {
console.log("Error:", error.message);
}
Important: Fetch only rejects on network errors, not HTTP errors (404, 500). Always check response.ok.
3. HTTP Methods
GET - Retrieve Data
// Get all users
const response = await fetch("https://api.example.com/users");
const users = await response.json();
// Get specific user
const response = await fetch("https://api.example.com/users/1");
const user = await response.json();
POST - Create Data
const newUser = {
name: "Alice",
email: "alice@example.com"
};
const response = await fetch("https://api.example.com/users", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(newUser)
});
const data = await response.json();
console.log("Created:", data);
Python comparison:
import requests
import json
new_user = {"name": "Alice", "email": "alice@example.com"}
response = requests.post(
"https://api.example.com/users",
json=new_user
)
data = response.json()
PUT - Update Data (Full Replace)
const updatedUser = {
id: 1,
name: "Alice Smith",
email: "alice.smith@example.com"
};
const response = await fetch("https://api.example.com/users/1", {
method: "PUT",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(updatedUser)
});
const data = await response.json();
console.log("Updated:", data);
PATCH - Update Data (Partial)
const updates = {
email: "newemail@example.com"
};
const response = await fetch("https://api.example.com/users/1", {
method: "PATCH",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(updates)
});
DELETE - Remove Data
const response = await fetch("https://api.example.com/users/1", {
method: "DELETE"
});
if (response.ok) {
console.log("User deleted");
}
4. Request Options
Headers
const response = await fetch(url, {
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer your-token-here",
"Accept": "application/json",
"Custom-Header": "value"
}
});
Request Body
// JSON
const response = await fetch(url, {
method: "POST",
body: JSON.stringify({ name: "Alice" })
});
// Form data
const formData = new FormData();
formData.append("name", "Alice");
formData.append("file", fileInput.files[0]);
const response = await fetch(url, {
method: "POST",
body: formData
// Don't set Content-Type header (browser sets it automatically)
});
// URL encoded
const params = new URLSearchParams();
params.append("name", "Alice");
params.append("age", "30");
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: params
});
Other Options
const response = await fetch(url, {
method: "GET",
mode: "cors", // cors, no-cors, same-origin
cache: "no-cache", // default, no-cache, reload, force-cache
credentials: "include", // include, same-origin, omit
redirect: "follow", // follow, error, manual
referrerPolicy: "no-referrer"
});
5. Handling Errors
Network Errors
async function fetchData(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return await response.json();
} catch (error) {
if (error instanceof TypeError) {
console.log("Network error:", error.message);
} else {
console.log("Error:", error.message);
}
throw error; // Re-throw if needed
}
}
Timeout
Fetch doesn't have built-in timeout, use AbortController:
async function fetchWithTimeout(url, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
signal: controller.signal
});
clearTimeout(timeoutId);
return await response.json();
} catch (error) {
if (error.name === "AbortError") {
throw new Error("Request timeout");
}
throw error;
}
}
try {
const data = await fetchWithTimeout("https://api.example.com/slow", 3000);
} catch (error) {
console.log("Error:", error.message);
}
6. Working with JSON
JSON.stringify() - Object to JSON
const user = {
name: "Alice",
age: 30,
active: true
};
const json = JSON.stringify(user);
console.log(json); // '{"name":"Alice","age":30,"active":true}'
// Pretty print
const prettyJson = JSON.stringify(user, null, 2);
console.log(prettyJson);
/*
{
"name": "Alice",
"age": 30,
"active": true
}
*/
JSON.parse() - JSON to Object
const jsonString = '{"name":"Alice","age":30}';
const user = JSON.parse(jsonString);
console.log(user.name); // "Alice"
Handling Invalid JSON
async function fetchData(url) {
const response = await fetch(url);
const text = await response.text(); // Get as text first
try {
return JSON.parse(text);
} catch (error) {
console.log("Invalid JSON:", text);
throw new Error("Invalid JSON response");
}
}
7. Practical Examples
Example 1: Display User List
HTML:
<div id="users"></div>
JavaScript:
async function loadUsers() {
const container = document.querySelector("#users");
container.innerHTML = "Loading...";
try {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
const users = await response.json();
container.innerHTML = users.map(user => `
<div class="user">
<h3>${user.name}</h3>
<p>${user.email}</p>
</div>
`).join("");
} catch (error) {
container.innerHTML = `<p>Error: ${error.message}</p>`;
}
}
loadUsers();
Example 2: Search with Debounce
<input type="text" id="search" placeholder="Search users...">
<div id="results"></div>
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func(...args), delay);
};
}
async function searchUsers(query) {
if (!query) {
document.querySelector("#results").innerHTML = "";
return;
}
try {
const response = await fetch(`https://api.github.com/search/users?q=${query}`);
const data = await response.json();
document.querySelector("#results").innerHTML = data.items
.slice(0, 5)
.map(user => `<div>${user.login}</div>`)
.join("");
} catch (error) {
console.log("Error:", error);
}
}
const debouncedSearch = debounce(searchUsers, 500);
document.querySelector("#search").addEventListener("input", (e) => {
debouncedSearch(e.target.value);
});
Example 3: Post Form Data
<form id="user-form">
<input type="text" name="name" placeholder="Name" required>
<input type="email" name="email" placeholder="Email" required>
<button type="submit">Create User</button>
</form>
<div id="message"></div>
document.querySelector("#user-form").addEventListener("submit", async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const user = Object.fromEntries(formData);
const message = document.querySelector("#message");
message.textContent = "Creating user...";
try {
const response = await fetch("https://jsonplaceholder.typicode.com/users", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(user)
});
const data = await response.json();
message.textContent = `User created with ID: ${data.id}`;
e.target.reset();
} catch (error) {
message.textContent = `Error: ${error.message}`;
}
});
Example 4: Loading Indicator
async function fetchWithLoading(url) {
const loader = document.querySelector("#loader");
const content = document.querySelector("#content");
loader.style.display = "block";
content.style.display = "none";
try {
const response = await fetch(url);
const data = await response.json();
content.innerHTML = JSON.stringify(data, null, 2);
content.style.display = "block";
} catch (error) {
content.innerHTML = `Error: ${error.message}`;
content.style.display = "block";
} finally {
loader.style.display = "none";
}
}
Example 5: 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) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.log(`Attempt ${i + 1} failed:`, error.message);
if (i === retries - 1) {
throw error; // Last attempt failed
}
// Wait before retry (exponential backoff)
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
}
try {
const data = await fetchWithRetry("https://api.example.com/data");
console.log(data);
} catch (error) {
console.log("All retries failed:", error.message);
}
8. CORS (Cross-Origin Resource Sharing)
What is CORS?
Browser security feature that restricts requests to different domains.
Same-origin: ✅ Allowed
https://example.com→https://example.com/api
Cross-origin: ❌ Blocked (unless server allows)
https://example.com→https://api.other.com
CORS Error
Access to fetch at 'https://api.other.com' from origin 'https://example.com'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header
Solutions
1. Server must enable CORS (backend fix):
// Server-side (Node.js/Express)
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
res.header("Access-Control-Allow-Headers", "Content-Type");
next();
});
2. Use CORS proxy (development only):
const proxyUrl = "https://cors-anywhere.herokuapp.com/";
const apiUrl = "https://api.other.com/data";
const response = await fetch(proxyUrl + apiUrl);
3. Server-side request (Node.js, Python):
- Make request from your server (no CORS restriction)
- Your frontend requests your server
9. Practical Exercises
Exercise 4.8.1: GitHub User Finder
Build an app that:
- Takes username input
- Fetches GitHub user data
- Displays profile info and repos
- Handles errors (user not found)
API: https://api.github.com/users/{username}
Exercise 4.8.2: Todo API Client
Create a todo app using JSONPlaceholder API:
- Fetch and display todos
- Add new todo (POST)
- Mark todo complete (PATCH)
- Delete todo (DELETE)
API: https://jsonplaceholder.typicode.com/todos
Exercise 4.8.3: Weather App
Build a weather app:
- Get user location
- Fetch weather data
- Display current weather and forecast
- Handle loading and errors
API: OpenWeatherMap or similar
Exercise 4.8.4: Image Search
Create image search:
- Search query input
- Fetch images from Unsplash API
- Display results in grid
- Infinite scroll (load more)
10. Knowledge Check
Question 1: What does AJAX stand for and what does it do?
Show answer
Asynchronous JavaScript And XML. Allows updating parts of a web page without full reload by fetching data from server asynchronously.Question 2: What's the difference between response.json() and response.text()?
Show answer
response.json() parses response as JSON and returns object. response.text() returns raw text string.Question 3: When does fetch() reject?
Show answer
Only on network errors. HTTP errors (404, 500) don't reject. Always check `response.ok` for HTTP status.Question 4: How do you send JSON data in a POST request?
Show answer
Set method to "POST", Content-Type header to "application/json", and body to JSON.stringify(data).Question 5: What is CORS and why does it exist?
Show answer
Cross-Origin Resource Sharing. Browser security feature that restricts cross-origin requests to prevent malicious sites from accessing user data.11. Key Takeaways
- AJAX enables dynamic updates without page reload
- Fetch API is modern way to make HTTP requests
- Always check
response.okbefore parsing (fetch doesn't reject on HTTP errors) - Use async/await for cleaner async code
- HTTP methods: GET (retrieve), POST (create), PUT/PATCH (update), DELETE (remove)
- Set Content-Type header to "application/json" when sending JSON
- Use JSON.stringify() to convert object to JSON, JSON.parse() to parse JSON
- Handle loading states and errors for better UX
- CORS restricts cross-origin requests (server must allow)
- Use AbortController for request timeout
12. Further Resources
Documentation:
Free APIs for Practice:
- JSONPlaceholder - Fake REST API
- GitHub API - Real API, no auth needed
- OpenWeatherMap - Weather data
Next Steps
Excellent! You can now build data-driven applications with external APIs.
In Lesson 4.9: ES6+ Modern Features, you'll learn modern JavaScript features like destructuring, modules, optional chaining, and more.
Next: Lesson 4.9 - ES6+ Modern Features →