Fetch API Tutorial

fetch() is an upgraded version of XMLHttpRequest, which is used to make HTTP requests in JavaScript scripts.

The browser provides this object natively. This chapter introduces its usage in detail.

Basic usage

The function of fetch() is basically the same as XMLHttpRequest, but there are three main differences.

(1) fetch() uses Promise instead of callback function, so it greatly simplifies the writing and makes writing more concise.

(2) fetch() adopts modular design, API is scattered on multiple objects (Response object, Request object, Headers object), which is more reasonable; in contrast, the API design of XMLHttpRequest is not very good, input , Output, and status are all managed in the same interface, which makes it easy to write very messy code.

(3) fetch() processes data through a data stream (Stream object), which can be read in blocks, which is conducive to improving website performance and reducing memory usage. It is very useful for scenarios where large files are requested or the network speed is slow. The XMLHTTPRequest object does not support data streaming. All data must be stored in the cache. Block reading is not supported. You must wait for all to be obtained before spitting it out at once.

In terms of usage, fetch() accepts a URL string as a parameter, sends a GET request to the URL by default, and returns a Promise object. Its basic usage is as follows.

fetch(url)
  .then(...)
  .catch(...)

The following is an example to get JSON data from the server.

fetch("https://api.github.com/users/ruanyf")
  .then((response) => response.json())
  .then((json) => console.log(json))
  .catch((err) => console.log("Request Failed", err));

In the above example, the response received by fetch() is a Stream object, response.json( ) is an asynchronous operation that takes out all the content and converts it into a JSON object.

Promise can be rewritten using await syntax to make the semantics clearer.

async function getJSON() {
  let url = "https://api.github.com/users/ruanyf";
  try {
    let response = await fetch(url);
    return await response.json();
  } catch (error) {
    console.log("Request Failed", error);
  }
}

In the above example, the await statement must be placed in try...catch in order to catch errors that may occur in asynchronous operations.

The following text uses the wording of await instead of the wording of .then().

Response object: handle HTTP response

Synchronous attributes of the Response object

After the fetch() request is successful, you will get a Response object. It corresponds to the HTTP response of the server.

const response = await fetch(url);

As mentioned earlier, the data contained in the Response is read asynchronously through the Stream interface, but it also contains some synchronous attributes, which correspond to the header information (Headers) of the HTTP response, which can be read immediately.

async function fetchText() {
  let response = await fetch("/readme.txt");
  console.log(response.status);
  console.log(response.statusText);
}

In the above example, response.status and response.statusText are the synchronous properties of Response, which can be read immediately.

The header information attributes are as follows.

Response.ok

The Response.ok property returns a boolean value, indicating whether the request is successful, true corresponds to the HTTP request status code 200 to 299, and false corresponds to other status codes.

Response.status

The Response.status property returns a number indicating the status code of the HTTP response (for example, 200, indicating a successful request).

Response.statusText

The Response.statusText property returns a string representing the status information of the HTTP response (for example, after the request is successful, the server returns "OK").

Response.url

The Response.url property returns the requested URL. If the URL has a redirect, this attribute returns the final URL.

Response.type

The Response.type property returns the type of request. The possible values ​​are as follows:

  • basic: Normal request, that is, same-source request.
  • cors: Cross-domain request.
  • error: Network error, mainly used for Service Worker.
  • opaque: If the type attribute of the fetch() request is set to no-cors, this value will be returned, see the request section for details. Indicates that a simple cross-domain request is issued, similar to the type of cross-domain request in the form of <form>.
  • opaqueredirect: If the redirect attribute of the fetch() request is set to manual, this value will be returned, see the request section for details.

Response.redirected

The Response.redirected property returns a boolean value indicating whether the request has been redirected.

Determine whether the request is successful

After fetch() sends a request, there is an important point to note: fetch() will report an error only when there is a network error or cannot connect. In other cases, no error will be reported, but the request is considered successful.

This means that even if the status code returned by the server is 4xx or 5xx, fetch() will not report an error (that is, the Promise will not change to the rejected status).

Only by using the Response.status property to get the true status code of the HTTP response, can it be judged whether the request is successful. Please see the example below.

async function fetchText() {
  let response = await fetch("/readme.txt");
  if (response.status >= 200 && response.status < 300) {
    return await response.text();
  } else {
    throw new Error(response.statusText);
  }
}

In the above example, the response.status attribute must be equal to 2xx (200~299) before the request is considered successful. There is no need to consider URL redirection (status code is 3xx), because fetch() will automatically convert the redirected status code to 200.

Another way is to determine whether response.ok is true.

if (response.ok) {
  // The request is successful
} else {
  // Request failed
}

Response.headers attribute

The Response object also has a Response.headers property, which points to a Headers object corresponding to all the headers of the HTTP response.

Headers objects can be traversed using for...of loops.

const response = await fetch(url);

for (let [key, value] of response.headers) {
  console.log(`${key}: ${value}`);
}

// or
for (let [key, value] of response.headers.entries()) {
  console.log(`${key}: ${value}`);
}

The Headers object provides the following methods to manipulate headers.

  • Headers.get(): Return the key value according to the specified key name.
  • Headers.has(): Returns a Boolean value indicating whether a certain header is included.
  • Headers.set(): Set the specified key name as a new key value, if the key name does not exist, it will be added.
  • Headers.append(): add headers.
  • Headers.delete(): delete headers.
  • Headers.keys(): returns a iterator that can traverse all keys in turn.
  • Headers.values(): returns a iterator that can traverse all key values ​​in turn.
  • Headers.entries(): returns a iterator that can traverse all key-value pairs ([key, value]) in turn.
  • Headers.forEach(): Traverse the headers in turn, each header will execute the parameter function once.

Some of the above methods can modify the headers because they are inherited from the Headers interface. For HTTP responses, modifying the header is of little significance, and many headers are read-only, and browsers do not allow modification.

Among these methods, the most commonly used is response.headers.get(), which is used to read the value of a certain header.

let response = await fetch(url);
response.headers.get("Content-Type");
// application/json; charset=utf-8

The Headers.keys() and Headers.values() methods are used to traverse the header keys and key values ​​respectively.

// key name
for (let key of myHeaders.keys()) {
  console.log(key);
}

// key value
for (let value of myHeaders.values()) {
  console.log(value);
}

The Headers.forEach() method can also traverse all key values ​​and key names.

let response = await fetch(url);
response.headers.forEach((value, key) => console.log(key, ":", value));

How to read content

The Response object provides different reading methods according to the different types of data returned by the server.

  • response.text(): Get a text string.
  • response.json(): Get a JSON object.
  • response.blob(): Get a binary Blob object.
  • response.formData(): Get the FormData form object.
  • response.arrayBuffer(): Get a binary ArrayBuffer object.

The above five read methods are all asynchronous and all return Promise objects. You must wait until the end of the asynchronous operation to get the complete data returned by the server.

response.text()

response.text() can be used to get text data, such as HTML files.

const response = await fetch("/users.html");
const body = await response.text();
document.body.innerHTML = body;

response.json()

response.json() is mainly used to obtain the JSON data returned by the server. The example has been given earlier.

response.formData()

response.formData() is mainly used in Service Worker to intercept the form submitted by the user, modify some data, and then submit it to the server.

response.blob()

response.blob() is used to get binary files.

const response = await fetch("flower.jpg");
const myBlob = await response.blob();
const objectURL = URL.createObjectURL(myBlob);

const myImage = document.querySelector("img");
myImage.src = objectURL;

The above example reads the image file flower.jpg and displays it on the web page.

response.arrayBuffer()

response.arrayBuffer() is mainly used to obtain streaming media files.

const audioCtx = new window.AudioContext();
const source = audioCtx.createBufferSource();

const response = await fetch("song.ogg");
const buffer = await response.arrayBuffer();

const decodeData = await audioCtx.decodeAudioData(buffer);
source.buffer = buffer;
source.connect(audioCtx.destination);
source.loop = true;

The above example is an example of response.arrayBuffer() getting the audio file song.ogg and playing it online.

Response.clone()

The Stream object can only be read once, and it is gone after reading. This means that only one of the five reading methods in the previous section can be used, otherwise an error will be reported.

let text = await response.text();
let json = await response.json(); // report an error

The above example uses response.text() first, and then reads the Stream. After calling response.json(), there is no content to read, so an error is reported.

The Response object provides the Response.clone() method to create a copy of the Response object for multiple reads.

const response1 = await fetch("flowers.jpg");
const response2 = response1.clone();

const myBlob1 = await response1.blob();
const myBlob2 = await response2.blob();

image1.src = URL.createObjectURL(myBlob1);
image2.src = URL.createObjectURL(myBlob2);

In the above example, response.clone() made a copy of the Response object, and then read the same image twice.

The Response object also has a Response.redirect() method, which is used to redirect the Response result to the specified URL. This method is generally only used in Service Worker, so I won't introduce it here.

Response.body attribute

The Response.body property is the underlying interface exposed by the Response object and returns a ReadableStream object for user operations.

It can be used to read the content in chunks, and one of the applications is to display the progress of the download.

const response = await fetch("flower.jpg");
const reader = response.body.getReader();

while (true) {
  const { done, value } = await reader.read();

  if (done) {
    break;
  }

  console.log(`Received ${value.length} bytes`);
}

In the above example, the response.body.getReader() method returns a iterator. The read() method of this iterator returns an object each time, representing the content block read this time.

The done property of this object is a boolean value, used to judge whether it has been read; the value property is an arrayBuffer array, which represents the content of the content block, and the value.length property is the size of the current block.

The second parameter of fetch(): custom HTTP request

The first parameter of fetch() is the URL, and the second parameter can also be accepted as a configuration object to customize the HTTP request sent out.

fetch(url, optionObj);

The optionObj of the above command is the second parameter.

The method, header, and data body of the HTTP request are all set in this object. Here are some examples.

(1) POST request

const response = await fetch(url, {
  method: "POST",
  headers: {
    "Content-type": "application/x-www-form-urlencoded; charset=UTF-8",
  },
  body: "foo=bar&lorem=ipsum",
});

const json = await response.json();

In the above example, three attributes are used in the configuration object.

  • method: HTTP request method, POST, DELETE, PUT are all set in this property.
  • headers: An object used to customize the headers of HTTP requests.
  • body: The body of the POST request.

Note that some headers cannot be set through the headers property, such as Content-Length, Cookie, Host, etc. They are automatically generated by the browser and cannot be modified.

(2) Submit JSON data

const user = { name: "John", surname: "Smith" };
const response = await fetch("/article/fetch/post/user", {
  method: "POST",
  headers: {
    "Content-Type": "application/json;charset=utf-8",
  },
  body: JSON.stringify(user),
});

In the above example, the header Content-Type should be set to 'application/json;charset=utf-8'. Because the default is to send plain text, the default value of Content-Type is 'text/plain;charset=UTF-8'.

(3) Submit form

const form = document.querySelector("form");

const response = await fetch("/users", {
  method: "POST",
  body: new FormData(form),
});

(4) File upload

If there is a file selector in the form, you can use the way of writing in the previous example. The uploaded file is included in the entire form and submitted together.

Another method is to add files with scripts, construct a form, and upload, please see the example below.

const input = document.querySelector('input[type="file"]');

const data = new FormData();
data.append("file", input.files[0]);
data.append("user", "foo");

fetch("/avatars", {
  method: "POST",
  body: data,
});

When uploading a binary file, there is no need to modify the Content-Type of the header, the browser will automatically set it.

(5) Upload binary data directly

fetch() can also upload binary data directly, putting Blob or arrayBuffer data in the body attribute.

let blob = await new Promise((resolve) =>
  canvasElem.toBlob(resolve, "image/png")
);

let response = await fetch("/article/fetch/post/image", {
  method: "POST",
  body: blob,
});

fetch() Complete API of configuration object

The complete API of the second parameter of fetch() is as follows.

const response = fetch(url, {
  method: "GET",
  headers: {
    "Content-Type": "text/plain;charset=UTF-8",
  },
  body: undefined,
  referrer: "about:client",
  referrerPolicy: "no-referrer-when-downgrade",
  mode: "cors",
  credentials: "same-origin",
  cache: "default",
  redirect: "follow",
  integrity: "",
  keepalive: false,
  signal: undefined,
});

The bottom layer of fetch() request is the interface of Request() object, the parameters are exactly the same, so The above API is also the API of Request().

Among these attributes, headers, body, and method have been given examples before, and the following is an introduction to other attributes.

cache

The cache attribute specifies how to handle the cache. The possible values ​​are as follows:

  • default: The default value, first look for matching requests in the cache.
  • no-store: Directly request the remote server without updating the cache.
  • reload: directly request the remote server and update the cache.
  • no-cache: Compare the server resources with the local cache, and use the server resources only when there is a new version, otherwise use the cache.
  • force-cache: Cache priority. Only when there is no cache, will the remote server be requested.
  • only-if-cached: Only check the cache. If the cache does not exist, a 504 error will be returned.

mode

The mode attribute specifies the requested mode. The possible values ​​are as follows:

  • cors: The default value, allowing cross-domain requests.
  • same-origin: Only same origin requests are allowed.
  • no-cors: The request method is limited to GET, POST and HEAD, and only a limited number of simple headers can be used. Cross-domain complex headers cannot be added, which is equivalent to submitting a form and loading scripts in <script> , <img> Load pictures and other traditional cross-domain request methods.

credentials

The credentials attribute specifies whether to send cookies. The possible values ​​are as follows:

  • same-origin: The default value, cookies are sent when requesting from the same origin, but not when requesting across domains.
  • include: Cookies are always sent regardless of the same origin or cross-domain request.
  • omit: Never send.

For cross-domain requests to send cookies, the credentials attribute needs to be set to include.

fetch("http://another.com", {
  credentials: "include",
});

signal

The signal attribute specifies an AbortSignal instance, which is used to cancel the fetch() request, see the next section for details.

keepalive

The keepalive attribute is used when the page is uninstalled, to tell the browser to keep the connection in the background and continue to send data.

A typical scenario is that when the user leaves the web page, the script submits some statistical information about the user's behavior to the server. At this time, if the keepalive attribute is not used, the data may not be sent because the browser has already uninstalled the page.

window.onunload = function () {
  fetch("/analytics", {
    method: "POST",
    body: "statistics",
    keepalive: true,
  });
};

redirect

The redirect attribute specifies the processing method for HTTP redirects. The possible values ​​are as follows:

-follow: default value, fetch() follows HTTP jump. -error: If a jump occurs, fetch() will report an error. -manual: fetch() does not follow HTTP redirects, but the response.url property will point to a new URL, and the response.redirected property will change to true. The developer decides what to do in the future Jump.

integrity

The integrity attribute specifies a hash value, which is used to check whether the data returned in the HTTP response is equal to the preset hash value.

For example, when downloading a file, check whether the SHA-256 hash value of the file matches to ensure that it has not been tampered with.

fetch("http://site.com/file", {
  integrity: "sha256-abcdef",
});

referrer

The referrer attribute is used to set the referer header of the fetch() request.

This attribute can be any string, or it can be set to an empty string (that is, the referer header is not sent).

fetch("/page", {
  referrer: "",
});

referrerPolicy

The referrerPolicy attribute is used to set the rules of the Referer header. The possible values ​​are as follows:

  • no-referrer-when-downgrade: The default value, the Referer header is always sent, unless it is not sent when HTTP resources are requested from an HTTPS page.
  • no-referrer: Do not send the Referer header.
  • origin: The Referer header only contains the domain name, not the full path.
  • origin-when-cross-origin: The same-origin request Referer header contains the complete path, and the cross-domain request only contains the domain name.
  • same-origin: Cross-domain requests do not send Referer, same-origin requests send.
  • strict-origin: The Referer header only contains the domain name, and the HTTPS page does not send the Referer header when requesting HTTP resources.
  • strict-origin-when-cross-origin: The Referer header contains the full path for the same origin request, and only the domain name is included for cross-domain requests. This header is not sent when HTTPS pages request HTTP resources.
  • unsafe-url: No matter what the situation, always send the Referer header.

Cancel fetch() request

After the fetch() request is sent, if you want to cancel in the middle, you need to use the AbortController object.

let controller = new AbortController();
let signal = controller.signal;

fetch(url, {
  signal: controller.signal,
});

signal.addEventListener("abort", () => console.log("abort!"));

controller.abort(); // cancel

console.log(signal.aborted); // true

In the above example, first create a new AbortController instance, and then send a fetch() request. The signal property of the configuration object must specify to receive the signal controller.signal sent by the AbortController instance.

The controller.abort() method is used to signal the cancellation. At this time, the abort event will be triggered. This event can be monitored, and it can also be judged whether the cancel signal has been sent through the controller.signal.aborted property.

The following is an example of automatically canceling the request after 1 second.

let controller = new AbortController();
setTimeout(() => controller.abort(), 1000);

try {
  let response = await fetch("/long-operation", {
    signal: controller.signal,
  });
} catch (err) {
  if (err.name == "AbortError") {
    console.log("Aborted!");
  } else {
    throw err;
  }
}