Examples
These examples combine the earlier topics into complete, small interactions. Each includes loading, success, and failure handling.
1. Live search with debounce
const input = document.querySelector('#query');
const results = document.querySelector('#results');
let timer;
let controller;
input.addEventListener('input', () => {
clearTimeout(timer);
controller?.abort();
timer = setTimeout(async () => {
controller = new AbortController();
results.setAttribute('aria-busy', 'true');
try {
const response = await fetch(
`/search?q=${encodeURIComponent(input.value)}`,
{ signal: controller.signal }
);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const items = await response.json();
results.replaceChildren();
for (const item of items) {
const li = document.createElement('li');
li.textContent = item.title;
results.append(li);
}
} catch (error) {
if (error.name !== 'AbortError') {
results.textContent = 'Search is unavailable right now.';
}
} finally {
results.setAttribute('aria-busy', 'false');
}
}, 250);
});
2. Load more button
const button = document.querySelector('#load-more');
const list = document.querySelector('#list');
let nextPage = 2;
button.addEventListener('click', async () => {
button.disabled = true;
try {
const response = await fetch(`/items?page=${nextPage}`);
if (!response.ok) throw new Error('Could not load more items');
list.insertAdjacentHTML('beforeend', await response.text());
nextPage++;
} catch (error) {
showError(error.message);
} finally {
button.disabled = false;
}
});
3. Delete with confirmation
async function remove(id, row) {
if (!confirm('Delete this item?')) return;
const response = await fetch(`/items/${id}`, {
method: 'DELETE',
headers: { 'X-CSRF-TOKEN': csrfToken() },
});
if (response.status === 204 || response.ok) {
row.remove();
} else {
showError('Delete failed. Please try again.');
}
}
4. Polling with visibility awareness
async function refresh() {
if (document.hidden) return;
const response = await fetch('/api/status');
if (response.ok) render(await response.json());
}
setInterval(refresh, 30000);
document.addEventListener('visibilitychange', refresh);
5. Submit a form with feedback
form.addEventListener('submit', async (event) => {
event.preventDefault();
const button = form.querySelector('button');
button.disabled = true;
button.textContent = 'Saving...';
try {
const response = await fetch(form.action, {
method: 'POST',
body: new FormData(form),
headers: { Accept: 'application/json' },
});
if (!response.ok) throw new Error('Save failed');
form.reset();
flash('Saved successfully.');
} catch (error) {
flash(error.message, true);
} finally {
button.disabled = false;
button.textContent = 'Save';
}
});
Each example uses the same structure: prevent the default action, show a busy state, check the status, update the DOM, and restore the interface. Reuse that structure instead of inventing a new flow for every endpoint.
Back to course overview · Previous: Browser Compatibility · Next: Browser Support