Now that you know how to manipulate the DOM, let’s connect the page to real server data. fetch is the browser-native network helper, and async/await makes Promise flows readable line by line.
Key terms
- async/await: keywords that let you read Promises sequentially. Execution pauses on an
awaitline until that Promise settles. - AbortController: a tool that cancels network requests when they take too long.
Core ideas
- Promise: a box that says “I will deliver the result later.” It starts
pendingand settles intofulfilledorrejected. - async/await: a syntax that makes Promise chains read like natural steps.
awaitpauses the function until the Promise resolves or rejects. - fetch: the HTTP API inside browsers. Because a connection that reaches the network counts as “successful,” you must still inspect
response.ok. - Loading/error state: UI that tells the user what is happening. Combine skeletons, spinners, or text to reflect progress.
- AbortController: the mechanism for canceling slow requests. Pass the
signaltofetch, then trigger a timeout or cancel button when needed.
Code examples
async function loadPost(id) {
const response = await fetch(`/api/posts/${id}`);
if (!response.ok) {
throw new Error("Failed to load the post");
}
return response.json();
}
loadPost(1)
.then((post) => console.log(post.title))
.catch((error) => console.error(error.message));
response.ok reports whether the HTTP status is 200–299. The network might be fine while the server returns 404, so you must check it.
const status = document.querySelector(".status");
const list = document.querySelector(".post-list");
async function loadPosts() {
try {
if (status) status.textContent = "Loading";
const response = await fetch("https://jsonplaceholder.typicode.com/posts?_limit=5");
if (!response.ok) throw new Error("Network error");
const posts = await response.json();
if (list) {
list.innerHTML = posts
.map((post) => `<li class="post-item">${post.title}</li>`)
.join("");
}
if (status) status.textContent = "Done";
} catch (error) {
if (status) {
status.textContent = error instanceof Error ? error.message : "Unknown error";
}
}
}
loadPosts();
Even simple status text reassures the user. Later you can add skeletons or modals.
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 4000);
try {
const response = await fetch("/api/attendance", { signal: controller.signal });
clearTimeout(timeout);
const data = await response.json();
renderAttendance(data);
} catch (error) {
if (error.name === "AbortError") {
showToast("The network is slow. Please try again.");
}
}
When the network crawls—think dorm Wi‑Fi—canceling the request and asking the user to retry prevents the UI from looking frozen.
async function loadDashboard() {
const [profile, todos] = await Promise.all([
fetch("/api/profile").then((res) => res.json()),
fetch("/api/todos").then((res) => res.json()),
]);
return { profile, todos };
}
Promise.all is for independent requests. Once all of them resolve you receive the results in order.
Why it matters
- External data powers pages such as event registration portals, so loading and error states directly influence user trust.
- When you can explain the
async/awaitstructure, code reviews and demos become clearer: “we wait here, then update the screen there.” - On unstable networks,
AbortControllerprevents the app from appearing hung.
Practice
- Follow along: call the JSONPlaceholder
postsendpoint and update loading, success, and error text in that order. - Extend: fetch
/postsand/userssimultaneously withPromise.alland render cards with author names. - Debug: use an incorrect URL to trigger HTTP 404, compare behavior with and without the
response.okguard, and note the difference. - Done when: you can reproduce the flow of loading message → data render → error message or retry button without console warnings.
Wrap-up
With fetch plus async/await you can wire the frontend to the backend directly. Next we will organize the fetched data and split it into modules.
💬 댓글
이 글에 대한 의견을 남겨주세요