Everything from Lecture 8, condensed. Print it, stick it next to your screen.
POST /api/stalls/taco-bike/reviews HTTP/1.1 // method ยท target
Content-Type: application/json // headers
// empty line
{"author":"Ana","rating":5,"text":"โฆ"} // body
HTTP/1.1 201 Created // status
Location: /api/reviews/r17 // headers
{"id":"r17", โฆ} // body
Origin = scheme + host + port. The fragment (#โฆ) is never sent.
GET read | safe, idempotent |
POST create / action | neither: don't retry blindly |
PUT set at a URL ยท DELETE | idempotent |
PATCH change some fields | not guaranteed |
200 ยท 201 ยท 204 | OK ยท Created ยท no body |
400 ยท 401 ยท 403 ยท 404 | malformed ยท no or invalid credentials ยท valid but not allowed ยท not found |
409 ยท 415 ยท 422 ยท 429 | conflict ยท not JSON ยท invalid values ยท slow down |
500 ยท 503 | server bug ยท try again later |
One thread. Code on the stack runs to the end; then every microtask (promise callbacks, resuming after await); then one task (timer, event); maybe a render; repeat.
console.log("A");
setTimeout(() => console.log("B"), 0);
Promise.resolve().then(() => console.log("C"));
console.log("D"); // A D C B
getStalls()
.then((stalls) => render(stalls))
.catch((error) => showError(error))
.finally(() => hideSpinner());
try { render(await getStalls()); }
catch (error) { showError(error); }
finally { hideSpinner(); }
.then returns a new promise; return a value or a promise from it, throw to reject. await only in async functions and modules. Independent: await Promise.all([a(), b()]).
const response = await fetch("/api/stalls?tag=food");
response.status; // 200
response.ok; // 200โ299
response.headers.get("X-Total-Count");
const data = await response.json(); // second await: the body
A 404 or a 500 resolves. fetch rejects only without a response: network, CORS, abort. Body readers: json() ยท text() ยท blob(), once each response.
async function getJSON(url, options) {
const response = await fetch(url, options);
if (!response.ok) {
const body = await response.json().catch((e) => {
if (e.name === "AbortError") throw e;
return null; // an error page that isn't JSON
});
throw new HttpError(response, body); // .status, .body
}
return response.status === 204 ? null : response.json();
}
const params = new URLSearchParams();
if (tag !== "all") params.set("tag", tag);
params.set("q", "rock & roll"); // encoded for you
fetch(`/api/stalls?${params}`);
state.status = "loading"; // "loading" | "ready" | "error"
render();
try {
state.stalls = await getStalls();
state.status = "ready";
} catch (error) {
console.error(error);
state.error = describe(error); // a sentence, not "Failed to fetch"
state.status = "error";
}
render(); // empty = ready && stalls.length === 0
role="status" for messages ยท role="alert" for the error, always in the page, only its text changes ยท Try again = loadStalls(), focus moved first.
| no network, CORS | TypeError (message varies by browser) |
| 4xx / 5xx | resolves, ok === false |
| body isn't JSON | json() โ SyntaxError (on a 2xx: wrong URL) |
| aborted ยท timed out | AbortError ยท TimeoutError |
const data = Object.fromEntries(new FormData(form));
const review = { ...data, rating: Number(data.rating) };
const response = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(review),
});
Forget JSON.stringify โ [object Object] (400). Forget the header โ text/plain (415). A string number โ 422.
id), reset the form.body.fields[name] under each field, aria-invalid, keep the input.button.disabled = true before, false in finally.json()./api: postJSON("/stalls/taco-bike/reviews", review).catch (e) { if (e.status === 422) showErrors(e.body.fields); }A page may read a cross-origin response only if the server sends Access-Control-Allow-Origin. JSON POSTs and Authorization trigger a preflight (OPTIONS). The fix is on the server; mode: "no-cors" gives an unreadable response.
let controller = null;
input.addEventListener("input", async () => {
controller?.abort();
controller = new AbortController();
try {
render(await getJSON(url, { signal: controller.signal }));
} catch (error) {
if (error.name !== "AbortError") showError(error);
}
});
fetch(url, { signal: AbortSignal.timeout(8000) }); // a timeout
Keys and tokens in browser code are public. Server data is user input: textContent.