Fetch API Tutorial

Fetch API Tutorial

fetch()It is an upgraded version of XMLHttpRequest for making HTTP requests in JavaScript scripts.

Browsers provide this object natively. This article describes its usage in detail.

Basic usage

fetch()The functionality is basically the same as XMLHttpRequest, with three main differences.

  • fetch()Using Promise does not use callback functions, so the writing method is greatly simplified and more concise.

  • fetch()Adopting a modular design, the 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, and the input, output, and status are all in the same one. Interface management, easy to write very confusing code.

  • fetch()Data is processed through data streams (Stream objects), which can be read in blocks, which is beneficial to improve website performance and reduce memory usage. It is quite useful for scenarios where large files are requested or the network speed is slow. The XMLHTTPRequest object does not support data streams. All data must be stored in the cache, and block reading is not supported. You must wait for all the data to be obtained, and then spit it out at one time.

In usage, fetch()a URL string is accepted as a parameter, a GET request is issued to the URL by default, and a Promise object is returned. Its basic usage is as follows.


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

Below 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, what is fetch()received responseis a Stream object , which response.json()is an asynchronous operation that takes all the content and converts it to a JSON object.

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

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

In the above example, the awaitstatement must be placed try...catchinside to catch errors that may occur in asynchronous operations.

The writing method used in the following text is awaitnot used .then().

Response object: handles the HTTP response

Synchronized properties of the Response object

fetch()After the request is successful, a Response object is obtained. It corresponds to the server’s HTTP response.

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, corresponding 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.statusand response.statusTextis the synchronization property of Response, which can be read immediately.

The header information attributes are as follows.

Response.ok

Response.okThe property returns a boolean value, indicating whether the request is successful, truecorresponding to the HTTP request status codes 200 to 299, falsecorresponding to other status codes.

Response.status

Response.statusThe property returns a number representing the status code of the HTTP response (eg 200 for a successful request).

Response.statusText

Response.statusTextThe property returns a string representing the status of the HTTP response (eg, the server returns “OK” after the request is successful).

Response.url

Response.urlThe property returns the requested URL. If the URL has a jump, this property returns the final URL.

Response.type

Response.typeThe property returns the type of the request. Possible values ​​are as follows:

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

Response.redirected

Response.redirectedThe property returns a boolean value indicating whether the request has jumped.

Determine whether the request was successful

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

This means that even if the status code returned by the server is 4xx or 5xx, fetch()no error will be reported (ie the Promise will not become rejectedstatus ).

Only by Response.statusobtaining the real status code of the HTTP response through the attribute can we judge whether the request is successful. 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.statusrequest can be considered successful only if the attribute is equal to 2xx (200~299). There is no need to consider URL redirection (status code is 3xx), because fetch()the redirected status code will be automatically converted to 200.

Another way is to judge whether r esponse.okis true.

if (response.ok) {
  // request succeeded
} else {
  // request failed
}

Response.headers property

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

Headers objects can for...ofbe traversed using a loop.

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 for manipulating headers.

  • Headers.get(): Returns 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 the new key value, or add it if it does not exist.
  • Headers.append(): Add headers.
  • Headers.delete(): remove the header.
  • Headers.keys(): Returns a iterator that traverses all key names in turn.
  • Headers.values(): Returns a traverser that traverses all key values ​​in turn.
  • Headers.entries(): Returns a traverser that traverses all key-value pairs ([key, value]) in turn.
  • Headers.forEach(): The headers are traversed in sequence, each of which executes the parameter function once.

Some of the above methods can modify the header, that’s because it inherits from the Headers interface. For HTTP responses, modifying headers doesn’t make much sense, and many headers are read-only and browsers don’t allow them to be modified.

The most common of these methods is response.headers.get()to read the value of a header.

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

Headers.keys()and Headers.values()methods are used to iterate over the header’s key name and key value, respectively.

// key
for(let key of myHeaders.keys()) {
  console.log(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

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

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

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

response.text()

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

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

response.json()

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

response.formData()

response.formData()It 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()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()Mainly used to get 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()obtaining the audio file song.ogg and playing it online.

Response.clone()

A 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();  // exception

The above example is used first response.text(), and the Stream is read. Called later response.json(), there is no content to read, so an error is reported.

The Response object provides Response.clone()methods to create Responsea copy of the 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()a copy of the Responseobject is and the same image is read twice.

The Response object also has a Response.redirect()method for redirecting the Response result to the specified URL. This method is generally only used in Service Worker, and will not be introduced here.

Response.body Property

Response.bodyThe property is the underlying interface exposed by the Response object, and returns an ReadableStreamobject for the user to operate.

It can be used to read content in chunks, and one of the applications is to show 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 an iterator. The methods of this traverser read()return an object each time, representing the block of content read this time.

The property of this object doneis a boolean value used to determine whether it has been read; valuethe property is an arrayBuffer array representing the content of the content block, and the value.lengthproperty is the size of the current block.

fetch()The second parameter of : custom HTTP request

fetch()The first parameter is the URL, and it can also accept a second parameter, as a configuration object, to customize the HTTP request issued.

fetch(url, optionObj)

The above command optionObjis the second parameter.

The methods, headers, and data bodies of HTTP requests are all set in this object. Below are some examples.

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, the configuration object uses three properties.

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

Note that some headers cannot be headersset via properties, such as Content-Length, Cookie, , Hostetc. They are automatically generated by the browser and cannot be modified.

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-Typeshould be set to 'application/json;charset=utf-8'. Because plain text is sent by default, Content-Typethe default value is 'text/plain;charset=UTF-8'.

submit Form

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

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

File Upload

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

Another way is to add the file with a script, construct a form, upload it, 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
});

Content-TypeWhen uploading a binary file , the browser will automatically set it without modifying the header .

Upload binary data directly

fetch()You can also upload binary data directly, and put Blob or arrayBuffer data in the bodyattribute.

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

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

fetch()Full API for configuration objects

fetch()The full API for the second parameter 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
});

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

Among these attributes, headers, body, methodhave been given examples before, and the following is the introduction of other attributes.

cache

cacheProperty specifies how to handle caching. The possible values ​​are as follows:

  • default: The default value, first look for matching requests in the cache.
  • no-store: Requests the remote server directly and does not update the cache.
  • reload: Request the remote server directly and update the cache.
  • no-cache: Compare the server resources with the local cache, use the server resources only if there is a new version, otherwise use the cache.
  • force-cache: Cache takes precedence. 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, it will return a 504 error.

mode

modeThe attribute specifies the mode of the request. The possible values ​​are as follows:

  • cors: The default value, allowing cross-origin 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, and complex cross-domain headers cannot be added, which is equivalent to the request that can be issued by submitting a form.

credentials

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

  • same-origin: The default value. Cookies are sent for same-origin requests, but not for cross-origin requests.
  • include: Cookie is always sent regardless of same-origin request or cross-origin request.
  • omit: Never send.

To send cookies across domain requests, you need to set the credentialsattribute to include.

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

signal

signalThe property specifies an AbortSignal instance, which is used to cancel the fetch()request, as described in the next section.

keepalive

keepaliveThe attribute is used to tell the browser to keep the connection in the background and continue sending data when the page is unloaded.

A typical scenario is that when the user leaves the web page, the script submits some statistics about the user’s behavior to the server. At this time, if the keepaliveattribute is not used, the data may not be sent because the browser has already unloaded the page.

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

redirect

redirectThe property specifies the handling method for HTTP redirects. The possible values ​​are as follows:

  • follow: Default value, fetch()follow HTTP jump.
  • error: If a jump occurs, fetch()an error is reported.
  • manual:f etch()does not follow the HTTP jump, but the response.urlattribute will point to the new URL, and the response.redirectedattribute will change true, and the developer decides how to handle the jump in the future.

integrity

integrityThe attribute specifies a hash value that is used to check whether the data returned by the HTTP response is equal to this preset hash value.

For example, when downloading a file, check that the SHA-256 hash of the file matches to make sure it hasn’t been tampered with.

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

referrer

referrerAttributes are used to set fetch()request refererheaders.

This property can be any string, or it can be set to an empty string (ie no refererheaders are sent).

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

referrerPolicy

referrerPolicyAttributes are used to set Refererthe rules for the header. The possible values ​​are as follows:

  • no-referrer-when-downgrade: Default, always send the Referer header unless not when requesting HTTP resources from HTTPS pages.
  • 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: Same-origin request Referer header contains the full path, cross-origin request only contains the domain name.
  • same-origin: Cross-domain requests do not send Referer, and same-origin requests are sent.
  • strict-origin: The Referer header only contains the domain name, and the Referer header is not sent when an HTTPS page requests an HTTP resource.
  • strict-origin-when-cross-origin: The Referer header contains the full path for same-origin requests, and only the domain name for cross-origin requests. This header is not sent when HTTPS pages request HTTP resources.
  • unsafe-url: Always send the Referer header no matter what.

cancel the fetch() request

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

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

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

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

controller.abort(); // cancal

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

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

controller.abort()method is used to signal cancellation. At this time, an event will be triggered abort. This event can be monitored, or controller.signal.abortedwhether the cancellation signal has been issued can be judged through the property.

Below 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;
  }
}

Reference link