Asking a server
A web page often needs data that lives on a server. fetch(url) is the browser's built-in way to ask: it sends an HTTP request (the web's message format) and returns a promise, a placeholder for an answer that arrives later.
Sending the request
Lines 1 to 6 only define loadUser. Line 7 calls it. An async function runs like any other function until its first await, and it always returns a promise.
fetch("/api/users/7") starts an HTTP GET request for that address on the page's own server. It does not wait for the answer. It returns a promise at once, and that promise is pending: no result yet.
await meets a pending promise, so loadUser pauses right there and hands control back to the code that called it. Only this function waits: the page can still scroll, take clicks and run other code.
So line 8 runs next, and the console prints waiting... before any data has arrived. Then the script ends. The engine is free while the request travels over the network.
The response comes back
The server finds user 7 and sends back an HTTP response: the status line 200 OK, a header saying the body is JSON (a text format for data), and the body itself, the text {"id":7,"name":"Ada"}.
Once the status and headers arrive, fetch's promise fulfils (finishes with a value): a Response object. await hands it over, loadUser carries on at line 2, and res now holds the response. res.status is 200.
res.ok is true for a status from 200 to 299. fetch does not reject on 404 or 500, only when the request cannot be made at all, so check ok yourself. Here it is true: no throw.
res.json() reads the body and parses the JSON text into a JavaScript object. Reading can take time, so it returns a second promise. await waits for it, then user holds { id: 7, name: "Ada" }.
user.name is "Ada", so line 5 prints Ada. The console shows waiting... first and Ada second: code that waits on the network finishes later than the lines written after it.
When it goes wrong
Now run it when user 7 does not exist. The server replies 404 Not Found. fetch's promise still fulfils, but res.ok is false, so line 3 throws. loadUser's promise rejects, and with no catch the console reports the error.
What happens
// step 1/11 · fetch-idea
fetch(url) send an HTTP request to url
returns a promise, straight away
later the promise fulfils with a Response
defined by the WHATWG Fetch Standard
(built into browsers and Node.js 18+)
fetch's promise fulfils as soon as the status and headers arrive; the body may still be downloading. That is why reading it with res.json() or res.text() returns another promise. A body can be read only once. fetch rejects (usually with a TypeError) only when no response comes back: no connection, a blocked cross-site request or a bad address. If you cancel a request with an AbortController, it rejects with an AbortError instead. To handle every failure, wrap the awaits in try { ... } catch (err) { ... }, or call loadUser().catch(...).