Introduction
Few real applications keep all their data in the page. Weather comes from a weather service, your messages come from a server, and your settings should still be there tomorrow. This lesson connects everything you know about promises, JSON and the DOM, and shows how a page gets data and remembers it.
How it works
A request and a response. fetch(url) sends a request and gives a promise of the response. The response has a status code, and a body that you read with await response.json(). This takes two steps, because the body may still be arriving when the headers have come in.
const response = await fetch("/api/products");
if (!response.ok) {
throw new Error("Server answered " + response.status);
}
const products = await response.json();
Two different kinds of failure. This surprises nearly everybody. fetch rejects only when the request could not be made at all: no network, or the server could not be reached. If the server answers "404 Not Found" or "500 Server Error", that is still a response, and fetch succeeds. So you must check response.ok, which is true for status codes from 200 to 299.
| Status | Meaning |
|---|---|
| 200 | it worked |
| 201 | something was created |
| 400 | the request was wrong |
| 401, 403 | not logged in, or not allowed |
| 404 | not found |
| 500 | the server failed |
Sending data. A request can carry data:
await fetch("/api/notes", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: "Buy rice" }),
});
CORS. A browser only lets a page read a response from another website if that website allows it, using special headers. A "CORS error" in the console means that the other server did not give that permission. It cannot be fixed from the page.
The three states. Every request takes time and can fail. A good page shows "loading" and turns off the button, so that people do not click it again. Then it shows the result or a helpful error, and always turns the button back on.
Remembering data. localStorage keeps text in the browser, even after it is closed. It has setItem(key, value), getItem(key) (which gives null if there is nothing) and removeItem(key). It holds only strings, so objects go through JSON.stringify and JSON.parse. sessionStorage works in the same way, but forgets everything when the tab closes.
Storage can fail. It can be full or blocked by the browser's settings, and stored text may be damaged or out of date. So reads and writes belong in a try block. Never store passwords or other secrets there, because any script on the page can read it.
A note on these pages. StepVioz runs your code with no network, and in a frame that has no storage. That is a safety measure. So the examples and exercises use a stand-in called fakeFetch, which behaves like fetch and answers after a short delay. In your own projects, you would simply call fetch.
The example below tries to use real storage. Here it will report that storage is blocked, which is exactly the situation that careful code must handle:
Syntax
async function load(url) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error("Status " + response.status);
return await response.json();
} catch (err) {
console.log("Could not load:", err.message);
return null;
}
}
localStorage.setItem("theme", JSON.stringify("dark"));
const theme = JSON.parse(localStorage.getItem("theme"));
Example
Press the button to see the loading state, and then the result.
Line by line
fakeFetchreturns a promise of an object shaped like a real response.loadshows the loading state at once, and turns the button off.- The first
awaitwaits for the response, andresponse.okis checked before it is trusted. - The second
awaitreads the body as data. - The list is emptied and rebuilt, so pressing the button twice cannot duplicate it.
catchshows a message for any failure.finallyturns the button back on, whatever happened.
Expected result
After a click the page says "Loading..." with the button turned off. Half a second later it lists Pen and Notebook, and says "Loaded 2 products".
In the real world
Almost every app you use is doing this all the time. Sites save a draft of your form in localStorage so that a crash does not lose your work. Dark mode settings are often remembered in the same way. StepVioz saves your course progress in your browser in exactly this way.
Common mistakes
- Not checking
response.ok, so an error page is treated as data. - Forgetting the second
awaitonresponse.json(). - Storing an object without
JSON.stringify. It is saved as the text[object Object]. - Parsing stored text without a
try. One damaged value then breaks the whole page. - Leaving the button on while loading, so that impatient people send the same request several times.
Best practices
- Show all three states: loading, success and failure.
- Turn controls back on in
finally, so that a failure cannot leave them dead. - Keep each stored value small, and give it a clear name.
- Treat everything from a server or from storage as untrusted, and check its shape.
Summary
fetch returns a promise of a response. Check response.ok, then read the body with json(). Show loading and error states. localStorage remembers strings across visits, so use JSON, and wrap storage in try.
Quick check
Key takeaways
- fetch only rejects when the request could not be made, so always check response.ok.
- Every data request has three states to show: loading, success and failure.
- localStorage stores only strings, so use JSON, and expect it to fail sometimes.
Practice problems for this lesson
My notes
Write down what you want to remember from this lesson. Notes are saved on this device as you type, and appear on your dashboard.