JS Sketchbooksee JavaScript think ✏️
← back to phase 6

6.9 — Checkpoint: the Pokédex fetcher

Checkpoint. One function on the left — showPokemon — quietly contains the entire phase: the non-blocking thread (6.1) parking at awaits (6.6) while the event loop (6.2) and its express lane (6.5) shuttle promise reactions (6.4), a two-await fetch with its guard (6.7), errors falling into one catch (5.8 × async), and a finally that always signs off.

Every data-driven screen you've ever used — and every API test you'll ever write — is this exact flow: loading → data or error → done. Walk it once more with commentary, then build it twice with your own hands.

watch it happen
async function showPokemon(id) {
  console.log("loading…");
  try {
    const res = await fetch(BASE + id);
    if (!res.ok) {
      throw new Error("HTTP " + res.status);
    }
    const p = await res.json();
    console.log(p.name + " #" + p.id);
  } catch (err) {
    console.log("error: " + err.message);
  } finally {
    console.log("done");
  }
}

State one: "loading…" prints BEFORE any await — synchronously, instantly. Users (and test logs) should never stare at silence. Then the function parks at the first await, thread free, world turning.

the pokédex screen — a UI is just states made visible⏳ loading…state 1 of every fetching UI: tell the human something is happeningloading…
under the hood

The deeper story, with the real names for things — this part is what turns “I saw it” into “I can explain it.”

Why fakes again in the exercises: determinism — the same reason professional test suites mock network calls. A test that depends on pokeapi.co's uptime and latency is a flaky test; a faked response with the same shape tests your logic with none of the randomness. Lesson 11.10 gives this practice its industrial name (route.fulfill); today you're already doing it.

The loading/error/done trio is also the anatomy of test-friendly UI: each state is observable — a spinner, an error banner, a rendered list. Playwright tests assert exactly these observations ("expect the spinner, then expect the list"). Apps built without visible states are the ones that are miserable to test — you'll say this in interviews someday.

Phase complete. The full picture, one breath. One thread. Blocking forbidden. The environment waits. Queue and express lane feed an empty stack. Promises are receipts. await parks frames. Combinators overlap independent waits. fetch rides it all. That paragraph is your teach-back — and a senior-engineer answer to "explain async JavaScript."

your turn

⌨️ part 1 — the fetcher that survives a 404

Build the full loading → error → done flow against a fake API that FAILS — your code must report the failure gracefully and always sign off.

requirements:

  • A fakeFetch(url) that resolves to { ok: false, status: 404 } — the server couldn't find it.
  • An async load(): print "loading…"; in a try, await the fetch, and if not ok, throw new Error("HTTP " + status).
  • In the catch, print "error: " + message; in a finally, print "done". Call load().

when you press RUN, the console must show exactly:

loading…
error: HTTP 404
done

⌨️ part 2 — two pokémon, one wait

Fetch two records in PARALLEL from a fake API and print both names — using the start-first, all-second reflex from 6.8 and the two-await shape from 6.7.

requirements:

  • A fakeFetch(name) returning a promise of { ok: true, json: () => Promise.resolve({ name }) } — it echoes the name you ask for.
  • An async loadTwo(): START both fetches for "pikachu" and "squirtle" (no await yet!), then await Promise.all of the pair.
  • Await each response's .json() (another all is fine), and print the two names, pikachu first.

when you press RUN, the console must show exactly:

pikachu
squirtle

✏️ Quick check 1

Type the FULL output, space-separated:

async function go() {
  console.log("loading");
  try {
    const res = await Promise.resolve({ ok: false, status: 500 });
    if (!res.ok) throw new Error("HTTP " + res.status);
    console.log("data");
  } catch (e) {
    console.log(e.message);
  } finally {
    console.log("done");
  }
}
go();

✏️ Quick check 2

Ten independent fetches, ~100ms each. Roughly how many ms with start-first + Promise.all? Type the number.

✏️ Quick check 3

Test suites fake network responses instead of calling real APIs mainly for ___ — type the word (hint: the opposite of flaky).

teach it back

🗣️ Now teach it back

The grand tour, out loud: explain how one JavaScript thread shows a loading spinner, fetches data from a server, handles a 404, and renders — naming every piece of machinery it rides on the way.

Write it as if your friend is sitting next to you. Saved to your journal — future-you will use these notes to teach others.

a few sentences, minimum — you’ve got this
to remember
Every fetching UI (and API test) = loading → data | error → done — with finally as the always-runs sign-off.
The resilient shape: sync loading print → try { await head → guard → await body → render } catch { render error } finally { done }.
Many independent requests: start first, Promise.all — the slowest, not the sum. Fakes give determinism; Playwright will call it route.fulfill.
next: 7.1 The DOM tree →