From a local function to a service
An HTTP request is a message from a client (here, your browser) to a server asking for a resource or an action. The server sends a response with a status code and, often, a body containing data. A URL identifies where to send the request. An API is an agreed interface: which URL to call, what input to send, and what response shape to expect.
For a read, fetch("/books") uses GET by default and asks the current site’s server for /books. fetch is provided by the browser; request in our example is just a parameter name we chose. Passing fetch as that argument uses the real operation. Passing a controlled async function lets us practise the same steps without contacting a server.
Common statuses include 200 for success, 404 for a missing resource, and 503 for temporary service unavailability. A network failure means no usable response arrived; a 503 response is a different outcome. A JSON body is still text until its parsing step succeeds.
A fulfilled fetch is not necessarily a successful response
Fetch makes a request and returns a promise for a Response. Network failures can reject it, while HTTP statuses such as 404 or 503 normally fulfill it. Check response.ok before using the body. .ok is true for status codes in the 200–299 range; status contains the numeric code.
Reading the body is another asynchronous operation: await response.json() to parse JSON. That can reject if the body is not valid JSON. It can also produce valid JSON with the wrong application shape. Status check, body parsing, and domain validation are separate steps. A Response body is normally consumed only once: store the parsed value if you need to inspect it again rather than calling response.json() a second time.
The function below accepts request as a parameter. Passing fetch in a real browser would make the HTTP call. Here a local async function returns a test double, an object implementing only the Response fields the code uses. It models the contract without using a live service. A test double is not a full browser Response and does not test whether a real server permits the browser request. CORS is the browser-enforced permission mechanism for reading responses from another origin (a different scheme, host, or port); the server must grant that permission. Client-side validation cannot grant it.
An adapter translates an external API into the small interface your app needs. Keep raw HTTP details there; the model should receive validated books rather than browser Response objects. Do not automatically retry writes: the server may have applied a write even if the response was lost.
Predict and trace
Write the output before running. Follow the values through each call; if your prediction differs, identify the first step that surprised you.
async function main() {
async function readJson(request, url) {
const response = await request(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
}
const ok = async () => ({
ok: true,
status: 200,
json: async () => ["Notes"]
});
const bad = async () => ({ ok: false, status: 503, json: async () => [] });
console.log((await readJson(ok, "/books"))[0]);
try {
await readJson(bad, "/books");
} catch (error) {
console.log(error.message);
}
}
main();Check your prediction
Notes
HTTP 503The bad response fulfills the request promise but fails the explicit status rule. Its body is not consumed.
Build it yourself
HTTP & fetch(): practise
Write loadCount(request), requesting /count, rejecting non-ok status, then returning body.count. Print 3 for a controlled successful response and a message for a rejected request.
// This first adapter assumes a valid count body after parsing.Hint 1: the idea
Check response.ok and await response.json separately.
Hint 2: a first step
Let a request rejection reach the caller’s try/catch.
Show one solution
async function main() {
async function loadCount(request) {
const response = await request("/count");
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const body = await response.json();
return body.count;
}
console.log(
await loadCount(async () => ({
ok: true,
json: async () => ({ count: 3 })
}))
);
try {
await loadCount(async () => {
throw new Error("Network unavailable");
});
} catch (error) {
console.log(error.message);
}
}
main();3
Network unavailableMake json throw an Error(“Invalid JSON”). Confirm it is a third failure path distinct from network and status errors. Next lesson adds body validation.
Test a changed requirement
Make json throw an Error(“Invalid JSON”). Confirm it is a third failure path distinct from network and status errors. Next lesson adds body validation.
Explain before moving on
Why can HTTP 503 reach the success path of await request? What does the double not test about a real service?
Going further
Read MDN’s Fetch guide after working through the examples. Use it as a reference when you meet an unfamiliar option; finishing every reference section is not required for this course.
Completion is saved in this browser only. It does not measure mastery.