Q1After running const response = await fetch(url);, what does response hold?
fetch — Getting Data from an API
Covers fetch and json() for getting JSON from a URL, response.ok for spotting failures, relative URLs, and loading two files at once with Promise.all.
So far, the exercises have written their data straight into the code. On a real site, prices and stock levels change, so the page asks the server's API (a URL that programs use to get data) every time it opens.
This article covers fetch, which gets data from a URL, and response.ok, which tells you whether the request succeeded.
Getting JSON from an API — fetch and json()
Say you want an order history page to show the orders stored on the server. The orders arrive as a JSON string, so you need a way to wait until they arrive and a step that turns the string back into an array. In the exercises, you'll fetch JSON files from this site's fixtures folder instead of a real API.
fetch (a function that asks the server for the data at a URL) returns a Promise that fulfills with a Response representing the server's reply. The status (a number that's 200 on success) and the headers (information about the response, sent ahead of the body) arrive first, and the body follows, so you await response.json() a second time to get the body.
// Fetch the orders JSON stored on this site (how to write the URL comes in the next section)
const response = await fetch("fixtures/en/orders.json");
console.log(response.status); // 200
// Read the whole body and convert it from JSON to an array
const orders = await response.json();
console.log(orders.length); // 3
console.log(orders[1].total); // 12600
// From here on, it's a regular array
const preparing = orders.filter((order) => order.status === "Preparing");
console.log(preparing.map((order) => order.id).join(", ")); // A-3002, A-3003
json() reads the body to the end and applies the same conversion as JSON.parse before returning the value, so you wait for the result with await. Once converted, orders is a regular array, so filter and map work on it directly.
Logging a Response Prints {}
In the exercise console, console.log(response) prints {}, just as it does for DOM elements. response.json() without await is a Promise, so it prints {} too. To check a Response, print its status; to check the body, print the value you get by awaiting json().
Writing URLs Based on the Page — Relative URLs
In the previous section, writing just "fixtures/en/orders.json" was enough to fetch the file. A full URL that starts with https:// includes the domain (the part after https://, such as runner.desktechlearn.com, that identifies the site), so you'd have to rewrite it whenever the site moved.
A relative URL (a URL written relative to the current page's URL) is resolved from the page's folder when it has no leading /, and from the top of the domain when it starts with /. The exercise code runs in runner.html, a hidden page on runner.desktechlearn.com, not on the article page.
// URL of the page this code runs in (the exercise console)
// https://runner.desktechlearn.com/tools/js-editor/runner.html
// No leading /: resolved from js-editor, the folder that holds runner.html
const fromPage = await fetch("fixtures/en/orders.json");
console.log(fromPage.url); // url is the URL that was actually requested
// https://runner.desktechlearn.com/tools/js-editor/fixtures/en/orders.json
// Leading /: resolved from the top of the domain
const fromRoot = await fetch("/tools/js-editor/fixtures/en/orders.json");
console.log(fromRoot.url === fromPage.url); // true (both point to the same file)
// If you leave out fixtures/en/, it looks directly inside js-editor
const noFolder = await fetch("orders.json");
console.log(noFolder.url);
// https://runner.desktechlearn.com/tools/js-editor/orders.json
- URLs that start with
/begin here
runner.html— the page where exercise code runs- URLs without a leading
/begin here
- Holds
orders.json,users.json, andproducts.json
A URL that starts with / points to the same file no matter which folder the page is in. A URL without it keeps working if you move the page and fixtures to another folder together. The table below shows where each URL starts and whether it finds the file.
| URL | Starts from | Finds the file? |
|---|---|---|
| fixtures/en/orders.json | js-editor | Yes |
| /tools/js-editor/fixtures/en/orders.json | Top of the domain | Yes (the same file) |
| orders.json | js-editor | No (not in js-editor) |
DevTools Resolves URLs from the Article Page
If you open the DevTools Console on the article page and run fetch("fixtures/en/orders.json"), the starting point is the article page's URL, so the request never reaches the fixtures. The article page's server returns the page's HTML with status 200 even for paths that don't exist, so the fetch may look like it worked, but what you get isn't JSON.
Spotting a Failed Request — response.ok and status
If you misspell a URL or the file has been deleted, the server replies that it couldn't find it. A reply still arrives, so fetch fulfills with a Response. Even inside try / catch, execution doesn't jump to catch; it moves on to the next line, which reads the body.
response.ok is true when status is between 200 and 299. 200 isn't the only success code (201 is one too), so use ok to decide whether the request succeeded. Servers usually return 404 (Not Found) for a missing file, but the exercise console's server returns 403 (Forbidden).
// A URL with orders misspelled as order
const response = await fetch("fixtures/en/order.json");
// Even when the file isn't found, fetch fulfills and returns a Response
console.log(response.ok); // false
console.log(response.status); // 404 or 403 (depends on the server; the exercise console returns 403)
// Convert the body to an array only when ok is true
if (response.ok) {
const orders = await response.json();
console.log(`Orders: ${orders.length}`);
} else {
console.log("Couldn't load your orders"); // Couldn't load your orders
}
In the right column, the body is an error description rather than JSON (XML, in the exercise console), so json() throws a SyntaxError on that line. The middle column checks ok first and shows a message without reading the body, so a failed request ends with a message on screen instead of an error.
Fetching Two Files at Once — async Functions and Promise.all
An admin screen that shows member and order counts side by side needs two JSON files, and neither depends on the other, so you load them in parallel (starting several tasks at once, then waiting), as covered in the async / await article. But if you call fetch twice and pass the results to Promise.all, what you get back is two Responses, not the bodies.
If you wrap everything from fetch to json() in one async function, the Promise you get by calling it fulfills with the body's value. Call that function twice and wait with Promise.all, and you get an array of the body values in the order you passed them.
// Wrap fetch through json() and return the body's value (null if not found)
async function loadJson(path) {
const response = await fetch(path);
if (!response.ok) {
return null;
}
return await response.json();
}
// Passing two fetch calls gets you Responses
const responses = await Promise.all([
fetch("fixtures/en/products.json"),
fetch("fixtures/en/orders.json"),
]);
console.log(responses[0].length); // undefined (not an array)
// Calling loadJson twice starts both at once, and Promise.all collects the body arrays
const [products, orders] = await Promise.all([
loadJson("fixtures/en/products.json"),
loadJson("fixtures/en/orders.json"),
]);
console.log(`Products: ${products.length} / Orders: ${orders.length}`); // Products: 4 / Orders: 3
If even one of the Promises you pass is rejected, Promise.all fails without handing over any of the other results. loadJson fulfills with null when ok is false, so if one file is missing, you still get the other array.
Knowledge Check
Answer each question one by one.
Q2With a path that doesn't exist, what's the state right after const res = await fetch(url); runs?
Q3In code running in /tools/js-editor/runner.html, where does fetch("users.json") look?