Lesson 3 of 4 · Network Plumbing & External APIs
async/await & fetch()
fetch() requests data; await waits for the answer without freezing the page.
This lesson is locked. You can read ahead, but finish earlier stops to earn progress.
Listen
Watch
Read
A promise is a future result, not the result itself
Fetching data takes time. fetch(url) starts the request and immediately returns a Promise. It does not hand you the JSON yet. An async function can use await to pause its own continuation until that promise settles while the browser stays available for clicks, scrolling and painting.
Predict this order before running it inside your linked app.js:
async function inspectJoke() {
console.log('1: starting request');
const response = await fetch('https://official-joke-api.appspot.com/random_joke');
console.log('3: headers arrived', response.status);
}
inspectJoke();
console.log('2: page code continues');Normally you see 1, then 2, then 3. Calling an async function returns a promise too. Defining a function without calling it sends no request. This exercise explains timing, not complete error handling.
Two waits do different work
await fetch waits for a response to become available, including headers. await response.json() reads the body and parses JSON into a JavaScript value. These are separate steps; the body might be delayed, malformed or in a different format.
response.ok is true for 200–299. An HTTP 404 or 500 is still a response: fetch normally resolves rather than rejects. Check status before treating a body as success. A successful 204 has no body, so do not blindly parse it as JSON. For this rehearsal choose an endpoint documented to return JSON on success.
Build a small display you can trace
Put a labelled output in your local HTML:
<section aria-labelledby="joke-heading"> <h2 id="joke-heading">Live joke</h2> <p id="joke-output" aria-live="polite">Not loaded yet.</p> </section> <script src="app.js" defer></script>
The script runs after the HTML is parsed, so it can select that output. In your practice script, study this complete single-record example, then build your own dashboard with your chosen data:
const output = document.getElementById('joke-output');
async function loadJoke() {
if (!output) return;
output.textContent = 'Loading…';
try {
const response = await fetch('https://official-joke-api.appspot.com/random_joke');
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const joke = await response.json();
if (typeof joke.setup !== 'string' || typeof joke.punchline !== 'string') {
throw new Error('Unexpected response shape');
}
output.textContent = `${joke.setup} ${joke.punchline}`;
} catch {
output.textContent = 'Could not load a joke. Try again later.';
}
}
loadJoke();Trace the steps: select → show loading → await response → reject non-OK status → parse → check required fields → display. On failure, catch replaces loading with a visible message. textContent treats returned strings as text, not HTML: never insert untrusted API text directly with innerHTML.
The type checks are simple shape validation. A valid JSON response might be an array instead of an object, or have different property names. Parse success is not proof it matches your product's needs. For an unknown response, inspect its documented shape and add appropriate guards before reading fields.
Guided practice: change one layer at a time
- Open the endpoint in a browser and identify setup and punchline. Predict which sentence the page will display.
- Load your locally served page. In DevTools Network, find the API request, its status and Response body. Compare those fields with what appeared on screen.
- Refresh: confirm a new request occurs rather than assuming the old displayed content is current.
- In the disposable rehearsal only, change the URL to a nonexistent path on the same service. Confirm a helpful error, not “undefined undefined”. Restore the correct URL.
- Add your own Load again control and attach the existing function as its click listener. Do not define a new function that never gets called.
Public services can be unavailable, rate-limited or change their CORS rules. Record actual evidence and retry sensibly; never forge response data to make a lesson look successful.
Understand common errors
| Symptom | Likely investigation |
|---|---|
| Promise printed instead of data | Missing await at the step that returns a promise |
| await syntax error | Put the code inside an async function; modern modules also allow top-level await |
| No request in Network | Was the function called? Is app.js linked and loaded? |
| Null output element | Check id spelling, script timing and the correct page |
| JSON parse error | Inspect content type/status and body; HTML is not JSON |
| undefined displayed | Inspect actual field names and whether response is an object or list |
| CORS error | Read the next lesson; changing parsing will not grant browser access |
Before the API dashboard activity
Choose a documented, key-free browser-friendly JSON endpoint. Identify its fields, implement the two awaits in the correct order, check response.ok, and render returned values visibly. Verify the real request in Network and the result in your page. The error-handling branch deepens retry and timeouts; the basic status check and visible failure belong in every API product, whichever branch you take.
Key terms
- Promise
- An IOU for a value that arrives later.
- async / await
- Mark a function that waits / wait without freezing.
- fetch
- The browser's way to make HTTP requests.
- res.ok
- True for 2xx statuses — check it yourself.
Summary — copy into your notes
- fetch returns a Promise; await unwraps it.
- Use await inside async functions; modern JavaScript modules also support top-level await.
- A 404 doesn't throw — check res.ok.