Learn by reading through in order

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
When Each Part Arrives, and the Two awaits
fetch(url) sendsa requestThe Promisestays pendingStatus 200 andheaders arriveawait fetch(…)returns a ResponseFull body arrives,parsed from JSONawait json()returns 3 orders
The fetch await finishes as soon as the headers arrive. The array only shows up after the json() await, which reads the body to the end.

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().

On a member management screen, get the member list from the server and list the names. The member list is available at fixtures/en/users.json.

① Fetch the member list and print the response status.

② Turn the body into an array and print the number of members.

③ Pull out just the names, join them with “, ”, and print the result.

(If it runs correctly, an explanation will appear.)

JavaScript / TypeScript Editor

Run code to see output

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
Where runner.html and fixtures Live
runner.desktechlearn.com — top of the domain (/)
  • URLs that start with / begin here
/tools/js-editor/ folder
  • runner.html — the page where exercise code runs
  • URLs without a leading / begin here
fixtures/en folder
  • Holds orders.json, users.json, and products.json
fixtures is inside js-editor, the same folder as runner.html. Whether the URL starts with / decides where the lookup begins.

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.

URLStarts fromFinds the file?
fixtures/en/orders.jsonjs-editorYes
/tools/js-editor/fixtures/en/orders.jsonTop of the domainYes (the same file)
orders.jsonjs-editorNo (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.

On the admin screen of a homeware shop, add up the product prices. brokenPath holds a product-list URL that leaves out the fixtures/en/ folders.

① Fetch brokenPath and print whether the status is 200 (true / false).

② Fix the URL so it starts from the page's folder, fetch it, and print the number of products.

③ Fetch with a URL that starts from the top of the domain, and print the total price.

JavaScript / TypeScript Editor

Run code to see output

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
}
Three Outcomes of the ok Check
await fetch(url)still fulfillsorders.jsonstatus 200order.jsonstatus 403, etc.order.jsonstatus 403, etc.ok is truecall json()ok is falsedon't call json()skip ok andcall json()get an arrayof 3 ordersshow an errormessageSyntaxErrorbody isn't JSON
fetch fulfills even when the file doesn't exist. Calling json() on a body that isn't JSON throws a SyntaxError.

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.

Load the products and reviews to show on a product page. The reviews haven't been published yet.

① Fetch fixtures/en/products.json and print whether it succeeded (true / false).

② Fetch fixtures/en/reviews.json and print whether it succeeded.

③ If ② failed, print “No reviews yet”; if it succeeded, print the count in the form “Reviews: 5”.

④ If ① succeeded, turn the body into an array and print the name of the product with 0 in stock.

JavaScript / TypeScript Editor

Run code to see output

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
fetch Results Don't Include the Bodies
Call fetch()twice, pass bothDone when bothResponses arriveYou get2 Responsesresponses[0].lengthis undefinedCall loadJson()twice, pass bothEach call waitsthrough json()You get2 arraysproducts.lengthis 4
The top row finishes as soon as the Responses arrive. To collect the bodies, pass the results of a function that waits through json().

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.

On a sales summary screen, load members, orders, and reviews at the same time. The path array paths is already declared, and the reviews haven't been published.

① Write an async function loadList that returns the body's value on success and an empty array on failure.

② Run each path in paths through loadList, wait for them all together, and print a line like “Members: 4 / Orders: 3 / Reviews: 0”.

③ Use userId to find the member who placed the first order, and print their name.

JavaScript / TypeScript Editor

Run code to see output
QUIZ

Knowledge Check

Answer each question one by one.

Q1After running const response = await fetch(url);, what does response hold?

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?