You have already used fetch to load JSON. This lesson covers the rest of the tool: the options that turn a plain request into a POST with a body and headers, the pieces of a Request and a Response, reading a body, and cancelling a request that is taking too long. Several examples build Request and Response objects directly, so you can study them without needing a server.
fetch(url) makes a plain GET request. The second argument is an options object that changes that:
| Option | Controls |
|---|---|
method | "GET" (default), "POST", "PUT", "PATCH", "DELETE" |
headers | extra information about the request, like "Content-Type" |
body | the data to send (a string for JSON, or a FormData) |
signal | lets you cancel the request (see below) |
credentials | whether to send cookies: "same-origin" (default), "include", "omit" |
async function postScore() {
const response = await fetch("/api/scores", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Ava", score: 95 })
});
return response.json();
}
There is no Try It button for this one because it needs a server that accepts scores. The next examples let you see exactly what such a request holds, with no server at all.
new Request(url, options) builds the same request object fetch makes for you, without sending it. You can read what you set, and read the body back:
const request = new Request("https://example.com/api/scores", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Ava", score: 95 })
});
console.log(request.method);
console.log(request.url);
console.log(request.headers.get("content-type"));
request.json().then(data => console.log(data.score));
A Response is what comes back. You can build one yourself to practice with its parts: the status, the headers, and the body readers:
const response = new Response(JSON.stringify({ name: "Ava", score: 95 }), {
status: 201,
statusText: "Created",
headers: { "Content-Type": "application/json" }
});
console.log(response.status, response.statusText, response.ok);
console.log(response.headers.get("content-type"));
response.json().then(data => console.log(data.name));
| Property | Holds |
|---|---|
response.status | the number: 200, 404... |
response.statusText | the short text that goes with it |
response.ok | true for 200 to 299 |
response.headers | a Headers object (names are case-insensitive) |
response.url | the final address, after any redirects |
response.redirected | true if a redirect happened |
The body can be read in one form: .json(), .text(), .blob() (for files), or .formData(). It can be read only once. A second read throws an error, because the data has been used up. If you need it twice, make a .clone() first:
async function demo() {
const response = new Response("hello");
const copy = response.clone();
console.log(await response.text());
console.log(response.bodyUsed);
try {
await response.text();
} catch (error) {
console.log(error.name);
}
console.log(await copy.text());
}
demo();
A Headers object holds the headers. Names are case-insensitive, and you read and change them with methods:
const headers = new Headers({ "Content-Type": "application/json" });
headers.set("X-Student", "Ava");
console.log(headers.get("content-type"));
console.log(headers.has("x-student"), headers.has("x-other"));
for (const [name, value] of headers) {
console.log(name + ": " + value);
}
A request that takes too long can be cancelled with an AbortController. Give its signal to fetch, then call abort(). The pending fetch rejects with an error named AbortError:
async function demo() {
const controller = new AbortController();
controller.abort();
try {
await fetch("students.json", { signal: controller.signal });
} catch (error) {
console.log(error.name);
}
}
demo();
A common use is a timeout, or cancelling the old request when a user types a new search. AbortSignal.timeout(5000) makes a signal that aborts by itself after five seconds:
fetch(url, { signal: AbortSignal.timeout(5000) })
.catch(error => {
if (error.name === "TimeoutError") console.log("Took too long");
});
Here are the lessons from this section and the JSON one in a single function: check ok, throw a clear error, parse the JSON, and accept options:
async function getJSON(url, options) {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(url + " answered " + response.status);
}
return response.json();
}
async function main() {
const students = await getJSON("students.json");
console.log(students.length, "students");
try {
await getJSON("missing.json");
} catch (error) {
console.log(error.message);
}
}
main();
body must be a string (or FormData, a file...), so use JSON.stringify"Content-Type": "application/json" header, so the server does not know the body is JSON404 or 500 as a rejection (it is a normal response: check ok)fetch rejects with TypeError when it cannot connect at allHow do you send a JavaScript object as JSON in a POST request?
body: objectbody: JSON.stringify(object) and a JSON Content-Type headerbody: JSON.parse(object)Content-Type header.What happens if you call response.json() twice on the same response, and how do you avoid it?
TypeError, because the body can be read only once. Call response.clone() first if you need to read it twice.What does this log?
const c = new AbortController();
c.abort();
fetch("students.json", { signal: c.signal })
.catch(error => console.log(error.name));
AbortError. The signal was already aborted, so the request is cancelled and the promise rejects with that error name.Request with the PUT method and a body, then log its method and bodyResponse with status 404 and check what ok saysgetJSON on products.json and log the store name