- EN
- ID
Master Table of Contents
- See full index in [01. Introduction]
Who this chapter is for
- Learners who are starting to call APIs and handle real data flow
- Beginners who still feel async behavior is “random” and hard to reason about
- Anyone who has seen loading spinners, double requests, or race-condition bugs
What you’ll learn
- Why async code exists and why UI can’t “wait” for network responses
- Promise fundamentals and when chaining still makes sense
async/await, error handling, and practical loading-state patterns
Why this topic matters
In real apps, almost everything important is async: API requests, file uploads, auth checks, search suggestions. If async flow is unclear, your app feels buggy even when the code “looks fine.”
When async fundamentals are solid, you avoid the classic pain: stale data, flickering UI, and impossible-to-reproduce bugs.
Core concepts
Promise basics
pending,fulfilled,rejected.then(),.catch(),.finally()
Think of Promise as a value you get later, not right now.
async/await
- Makes asynchronous code look sequential
- Still uses Promises under the hood
Use async/await for readability, especially in multi-step request flows.
Error handling
- Use
try/catcharound awaited calls - Show friendly messages in UI
In production apps, errors are part of normal flow, not rare exceptions.
Step-by-step walkthrough
Step 1 — Start with a Promise-based fetch
Call one public endpoint and log the result.
Step 2 — Convert to async/await
Convert it to async/await, wrap with try/catch, and return parsed JSON.
Step 3 — Add UI state thinking
Track loading, success, empty, and error states explicitly.
Target in this chapter: make async behavior predictable, not surprising.
Practical examples
Example 1 — Promise chain
fetch("https://jsonplaceholder.typicode.com/todos/1")
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.error(error));
Expected behavior:
- Data logs on success
- Error logs if request fails
Example 2 — async/await
async function getTodo() {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/todos/1");
const data = await response.json();
console.log(data);
} catch (error) {
console.error("Request failed", error);
}
}
Real case:
- This pattern is the base for most frontend API service functions.
Common mistakes and how to avoid them
- Forgetting
await-> unresolved Promise leaks into your logic - Missing error handling -> failures become silent and hard to debug
- Triggering repeated requests (typing/search) -> debounce or guard requests
- Updating UI after stale response returns -> track request order or cancel outdated requests
Mini Project
- Build a weather lookup app with:
- loading state,
- success state,
- empty state,
- error state.
Bonus:
- Add a basic “retry” button when request fails.
Quick practice
- Convert one
.then()chain toasync/await - Add
try/catcharound API logic - Show a loading message before data arrives
- Explain in one sentence: why can stale responses break UI consistency?
Key takeaways
- Async flow is core to modern frontend behavior
async/awaitimproves clarity, but you still need solid state handling- Loading/error/empty states are product requirements, not optional polish
Next step
Continue to [09. React Fundamentals]
No Comments