LV1 · 0 XPLog in
Learning path

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

Video coming soon

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:

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:

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:

js
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

  1. Open the endpoint in a browser and identify setup and punchline. Predict which sentence the page will display.
  2. 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.
  3. Refresh: confirm a new request occurs rather than assuming the old displayed content is current.
  4. 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.
  5. 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

SymptomLikely investigation
Promise printed instead of dataMissing await at the step that returns a promise
await syntax errorPut the code inside an async function; modern modules also allow top-level await
No request in NetworkWas the function called? Is app.js linked and loaded?
Null output elementCheck id spelling, script timing and the correct page
JSON parse errorInspect content type/status and body; HTML is not JSON
undefined displayedInspect actual field names and whether response is an object or list
CORS errorRead 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.