✍️ Fetch API
The Fetch API is a modern way to make HTTP requests from the browser.
It is Promise-based, which makes handling async operations cleaner and easier.
The
fetch()function is available globally and lets you communicate with servers to get or send data.
📩 Sending a Request
fetch()needs only the URL:
fetch(url);Since it returns a Promise, you handle it using
then()andcatch():
fetch(url)
.then(response => {
// handle response
})
.catch(error => {
// handle error
});When the request completes, it returns a Response object.
📖 Reading the Response
To read JSON data, use
response.json():
fetch(url)
.then(response => response.json())
.then(data => console.log(data));Other response methods:
text()→ plain textblob()→ files/imagesformData()→ form dataarrayBuffer()→ binary data
💻 Handling HTTP Status
You can check the status using
statusorok:
const response = await fetch(url);
if (response.ok) {
console.log("Request successful");
} else {
throw new Error(`Error: ${response.status}`);
}response.okistruefor status codes 200–299.
🔅Fetch API example
We’ll fetch a list of users from this API:
<https://jsonplaceholder.typicode.com/users><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Fetch API Demo</title>
<script src="js/app.js" defer></script>
</head>
<body>
<ul id="content"></ul>
<p id="message"></p>
<p id="loading"></p>
</body>
</html>Elements used:
#content→ shows user list#message→ shows errors#loading→ shows loading text
1️⃣ Fetch users
const getUsers = async () => {
const response = await fetch('<https://jsonplaceholder.typicode.com/users>');
return response.json();
};2️⃣ Render users
const renderUsers = (users) => {
return users
.map(user => `<li>${user.name} (${user.email})</li>`)
.join('');
};3️⃣ Load data on page load
(async () => {
// Get references to DOM elements
const loading = document.getElementById('loading'); // Shows loading state
const content = document.getElementById('content'); // Displays user list
const message = document.getElementById('message'); // Displays error messages
// Show loading message before starting the API call
loading.textContent = 'Loading...';
try {
// Call the API to fetch users (async operation)
const users = await getUsers();
// Render the fetched users inside the content element
content.innerHTML = renderUsers(users);
} catch (error) {
// If any error occurs (network / API / parsing),
// show a user-friendly error message
message.textContent = 'Error fetching users';
} finally {
// This block always runs (success or error)
// Clear the loading message once the request is complete
loading.textContent = '';
}
})();<div class="infographic-fetchapi">
<style>
.infographic-fetchapi {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--good: #1a8a5f;
--bad: #c23b3b;
--bad-soft: #fbe6e6;
--mono-font: 'IBM Plex Mono', monospace;
--display-font: 'Fraunces', Georgia, serif;
box-sizing: border-box;
max-width: 880px;
margin: 0 auto;
font-family: 'Inter', system-ui, sans-serif;
color: var(--ink);
background: var(--paper);
overflow-x: hidden;
}
.infographic-fetchapi[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
--good: #4fbf8f;
--bad: #e06b6b;
--bad-soft: #351c1c;
}
.infographic-fetchapi * { box-sizing: border-box; }
.infographic-fetchapi h1 { font-family: var(--display-font); font-weight: 600; margin: 0; }
.infographic-fetchapi p { margin: 0; }
.infographic-fetchapi button { font-family: inherit; }
.fa-icon { width: 1em; height: 1em; display: inline-block; vertical-align: -0.15em; }
.fa-icon svg { width: 100%; height: 100%; display: block; }
.fa-wrap { padding: clamp(1.25rem, 5vw, 2.5rem) clamp(1.1rem, 5vw, 3rem) 2rem; }
.fa-hero { text-align: center; margin-bottom: 1.2rem; }
.fa-hero h1 { font-size: clamp(1.3rem, 4.2vw, 1.75rem); }
.fa-hero p { color: var(--ink-soft); margin-top: 0.4rem; font-size: 0.86rem; }
/* --- demo card --- */
.fa-demo { max-width: 460px; margin: 0 auto 1rem; border: 1px solid var(--line); border-radius: 10px; overflow: hidden; }
.fa-demo-head { padding: 0.5rem 0.9rem; background: var(--paper-alt); border-bottom: 1px solid var(--line);
font-family: var(--mono-font); font-size: 0.66rem; color: var(--ink-soft); }
.fa-demo-body { padding: 1rem 1.1rem; min-height: 8rem; }
.fa-loading { font-family: var(--mono-font); font-size: 0.72rem; color: var(--accent); min-height: 1.2em; margin-bottom: 0.4rem; }
.fa-message { font-size: 0.78rem; color: var(--bad); background: var(--bad-soft); border-radius: 6px; padding: 0.5rem 0.7rem;
margin-bottom: 0.5rem; display: none; }
.fa-message.is-shown { display: block; }
.fa-list { list-style: none; display: flex; flex-direction: column; gap: 0.4rem; }
.fa-list li { font-size: 0.78rem; padding: 0.4rem 0.6rem; border-radius: 5px; background: var(--paper-alt);
animation: fa-pop-in 0.2s ease; }
.fa-list li strong { color: var(--accent); }
.fa-list li span { color: var(--ink-soft); font-family: var(--mono-font); font-size: 0.7rem; }
@keyframes fa-pop-in { from { opacity: 0; transform: translateY(-3px); } to { opacity: 1; transform: translateY(0); } }
/* --- controls --- */
.fa-controls { display: flex; gap: 0.5rem; justify-content: center; flex-wrap: wrap; margin-bottom: 1.3rem; }
.fa-btn { display: flex; align-items: center; gap: 0.35rem; background: var(--accent); color: #fff; border: none;
border-radius: 6px; padding: 0.45rem 0.95rem; font-size: 0.76rem; font-weight: 700; cursor: pointer; }
.fa-btn:hover:not(:disabled) { opacity: 0.88; }
.fa-btn:disabled { background: var(--line); color: var(--ink-soft); cursor: not-allowed; }
.fa-btn.is-ghost { background: var(--paper); color: var(--ink-soft); border: 1px solid var(--line); }
.fa-btn.is-ghost:hover:not(:disabled) { border-color: var(--bad); color: var(--bad); }
.fa-btn .fa-icon { width: 13px; height: 13px; }
/* --- console --- */
.fa-console-panel { max-width: 480px; margin: 0 auto 0; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; }
.fa-console-head { display: flex; align-items: center; justify-content: space-between; padding: 0.5rem 0.75rem;
border-bottom: 1px solid var(--line); background: var(--paper-alt); font-family: var(--mono-font); font-size: 0.7rem;
font-weight: 700; color: #888; }
.fa-clear-btn { background: none; border: none; color: var(--ink-soft); font-family: var(--mono-font); font-size: 0.64rem;
cursor: pointer; padding: 0.15rem 0.4rem; border-radius: 4px; }
.fa-clear-btn:hover { color: var(--accent); background: var(--accent-soft); }
.fa-console { min-height: 7rem; max-height: 10rem; overflow-y: auto; background: #1e1e1e; padding: 0.5rem 0.7rem; }
.fa-console-line { font-family: var(--mono-font); font-size: 0.66rem; color: #d4d4d4; padding: 0.08rem 0; animation: fa-pop-in 0.2s ease; white-space: pre-wrap; }
.fa-console-line.is-echo { color: #7fb0ff; }
.fa-console-line.is-good { color: #9fd9a8; padding-left: 0.9rem; }
.fa-console-line.is-bad { color: #e88; padding-left: 0.9rem; }
.fa-console-line.is-finally { color: #d8a657; padding-left: 0.9rem; }
.fa-console-empty { font-family: var(--mono-font); font-size: 0.66rem; color: #6b6b6b; }
/* --- legend --- */
.fa-legend { display: flex; gap: 0.5rem; justify-content: center; flex-wrap: wrap; max-width: 620px; margin: 1rem auto 0; }
.fa-chip { display: flex; align-items: center; gap: 0.35rem; border: 1px solid var(--line); border-radius: 20px;
padding: 0.32rem 0.7rem; font-family: var(--mono-font); font-size: 0.64rem; color: var(--ink-soft);
transition: border-color 0.2s ease, background 0.2s ease, color 0.2s ease; }
.fa-chip.is-firing { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); font-weight: 700; }
.fa-chip.is-firing-bad { border-color: var(--bad); background: var(--bad-soft); color: var(--bad); font-weight: 700; }
.fa-note { text-align: center; font-size: 0.78rem; color: var(--ink-soft); padding-top: 1.4rem; max-width: 560px; margin: 0 auto; }
.fa-note strong { color: var(--ink); }
@media (prefers-reduced-motion: reduce) {
.infographic-fetchapi *, .infographic-fetchapi *::before, .infographic-fetchapi *::after {
animation-duration: 0.001ms !important; transition-duration: 0.001ms !important;
}
}
</style>
<div class="fa-wrap">
<div class="fa-demo">
<div class="fa-demo-head">index.html — #content, #message, #loading</div>
<div class="fa-demo-body">
<p class="fa-loading" id="faLoading"></p>
<p class="fa-message" id="faMessage"></p>
<ul class="fa-list" id="faContent"></ul>
</div>
</div>
<div class="fa-controls">
<button class="fa-btn" id="faRefetchBtn" type="button">
<span class="fa-icon" id="faRefetchIcon"></span>Fetch users
</button>
<button class="fa-btn is-ghost" id="faErrorBtn" type="button">
<span class="fa-icon" id="faErrorIcon"></span>Trigger error
</button>
</div>
<div class="fa-console-panel">
<div class="fa-console-head">
<span>>_ event log</span>
<button class="fa-clear-btn" id="faClearBtn" type="button">clear</button>
</div>
<div class="fa-console" id="faConsole">
<div class="fa-console-empty">// click a button below to run a real fetch</div>
</div>
</div>
<div class="fa-legend">
<span class="fa-chip" data-evt="try"><span class="fa-icon" id="faChipIcon0"></span>try</span>
<span class="fa-chip" data-evt="getUsers"><span class="fa-icon" id="faChipIcon1"></span>getUsers()</span>
<span class="fa-chip" data-evt="renderUsers"><span class="fa-icon" id="faChipIcon2"></span>renderUsers()</span>
<span class="fa-chip" data-evt="catch"><span class="fa-icon" id="faChipIcon3"></span>catch</span>
<span class="fa-chip" data-evt="finally"><span class="fa-icon" id="faChipIcon4"></span>finally</span>
</div>
</div>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-fetchapi')
|| document.querySelector('.infographic-fetchapi');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') {
dark = true;
} else if (bodyAttr === 'false') {
dark = false;
} else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
const ICONS = {
refresh: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-3-6.7"/><path d="M21 3v6h-6"/></svg>',
zapOff: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.513 4.856 13.12 2.17a.5.5 0 0 1 .86.46l-1.377 4.317"/><path d="M15.656 10H20a1 1 0 0 1 .78 1.63l-1.72 1.773"/><path d="M16.273 16.273 10.98 21.83a.5.5 0 0 1-.86-.46l1.5-4.706"/><path d="M17.8 17.8 2 22l4.2-15.8"/><path d="m2 2 20 20"/><path d="M8.35 8.35 4 10h4l-1.5 4.5"/></svg>',
play: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z"/></svg>',
download: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 15V3"/><path d="M18.4 9.6a9 9 0 0 1 1.657 9.786 9 9 0 0 1-8.985 4.591 9 9 0 0 1-8.985-4.591A9 9 0 0 1 5.7 9.5"/><path d="m7 10 5 5 5-5"/></svg>',
list: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12h.01"/><path d="M3 18h.01"/><path d="M3 6h.01"/><path d="M8 12h13"/><path d="M8 18h13"/><path d="M8 6h13"/></svg>',
xcircle: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg>',
check: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.801 10A10 10 0 1 1 17 3.335"/><path d="m9 11 3 3L22 4"/></svg>'
};
wrapper.querySelector('#faRefetchIcon').innerHTML = ICONS.refresh;
wrapper.querySelector('#faErrorIcon').innerHTML = ICONS.zapOff;
wrapper.querySelector('#faChipIcon0').innerHTML = ICONS.play;
wrapper.querySelector('#faChipIcon1').innerHTML = ICONS.download;
wrapper.querySelector('#faChipIcon2').innerHTML = ICONS.list;
wrapper.querySelector('#faChipIcon3').innerHTML = ICONS.xcircle;
wrapper.querySelector('#faChipIcon4').innerHTML = ICONS.check;
const loading = wrapper.querySelector('#faLoading');
const message = wrapper.querySelector('#faMessage');
const content = wrapper.querySelector('#faContent');
const refetchBtn = wrapper.querySelector('#faRefetchBtn');
const errorBtn = wrapper.querySelector('#faErrorBtn');
const consoleEl = wrapper.querySelector('#faConsole');
const clearBtn = wrapper.querySelector('#faClearBtn');
function logLine(text, cls) {
if (consoleEl.querySelector('.fa-console-empty')) consoleEl.innerHTML = '';
const line = document.createElement('div');
line.className = 'fa-console-line ' + (cls || '');
line.textContent = text;
consoleEl.appendChild(line);
consoleEl.scrollTop = consoleEl.scrollHeight;
}
function flashChip(evt, bad) {
const chip = wrapper.querySelector('.fa-chip[data-evt="' + evt + '"]');
if (!chip) return;
chip.classList.add(bad ? 'is-firing-bad' : 'is-firing');
clearTimeout(chip._t);
chip._t = setTimeout(() => chip.classList.remove('is-firing', 'is-firing-bad'), 500);
}
// --- the actual app logic, matching the source ---
const getUsers = async (url) => {
logLine("> fetch('" + url + "');", 'is-echo');
flashChip('getUsers');
const response = await fetch(url);
return response.json();
};
const renderUsers = (users) => {
return users.map(user =>
'<li><strong>' + user.name + '</strong><br><span>' + user.email + '</span></li>'
).join('');
};
// Real jsonplaceholder.typicode.com/users data, used only if the live
// fetch is blocked by this preview's sandbox (no external network access).
const MOCK_USERS = [
{ name: 'Leanne Graham', email: 'Sincere@april.biz' },
{ name: 'Ervin Howell', email: 'Shanna@melissa.tv' },
{ name: 'Clementine Bauch', email: 'Nathan@yesenia.net' },
{ name: 'Patricia Lebsack', email: 'Julianne.OConner@kory.org' },
{ name: 'Chelsey Dietrich', email: 'Lucio_Hettinger@annie.ca' }
];
async function fetchUsersFlow() {
loading.textContent = 'Loading...';
message.classList.remove('is-shown');
content.innerHTML = '';
logLine('loading.textContent = "Loading..."', 'is-good');
flashChip('try');
refetchBtn.disabled = true;
errorBtn.disabled = true;
try {
const users = await getUsers('https://jsonplaceholder.typicode.com/users');
logLine('response.json() \u25b8 ' + users.length + ' users received', 'is-good');
flashChip('renderUsers');
content.innerHTML = renderUsers(users);
logLine('content.innerHTML \u25b8 rendered ' + users.length + ' <li> items', 'is-good');
} catch (error) {
logLine('live fetch blocked in this preview \u25b8 ' + (error && error.message ? error.message : 'network error'), 'is-bad');
logLine('falling back to bundled sample data (same shape as the real API)', 'is-echo');
flashChip('renderUsers');
content.innerHTML = renderUsers(MOCK_USERS);
logLine('content.innerHTML \u25b8 rendered ' + MOCK_USERS.length + ' <li> items (sample data)', 'is-good');
} finally {
loading.textContent = '';
logLine('finally \u25b8 loading.textContent = ""', 'is-finally');
flashChip('finally');
refetchBtn.disabled = false;
errorBtn.disabled = false;
}
}
async function loadUsers(url) {
loading.textContent = 'Loading...';
message.classList.remove('is-shown');
content.innerHTML = '';
logLine('loading.textContent = "Loading..."', 'is-good');
flashChip('try');
refetchBtn.disabled = true;
errorBtn.disabled = true;
try {
const users = await getUsers(url);
logLine('response.json() \u25b8 ' + users.length + ' users received', 'is-good');
flashChip('renderUsers');
content.innerHTML = renderUsers(users);
logLine('content.innerHTML \u25b8 rendered ' + users.length + ' <li> items', 'is-good');
} catch (error) {
message.textContent = 'Error fetching users';
message.classList.add('is-shown');
logLine('catch \u25b8 ' + (error && error.message ? error.message : 'fetch failed'), 'is-bad');
logLine('message.textContent = "Error fetching users"', 'is-bad');
flashChip('catch', true);
} finally {
loading.textContent = '';
logLine('finally \u25b8 loading.textContent = ""', 'is-finally');
flashChip('finally');
refetchBtn.disabled = false;
errorBtn.disabled = false;
}
}
refetchBtn.addEventListener('click', () => {
fetchUsersFlow();
});
errorBtn.addEventListener('click', () => {
loadUsers('https://this-domain-does-not-exist-' + Date.now() + '.invalid/users');
});
clearBtn.addEventListener('click', () => {
consoleEl.innerHTML = '<div class="fa-console-empty">// click a button below to run a real fetch</div>';
});
// No auto-fetch on load — use the buttons above to trigger a real request.
})();
</script>