Fetch

The Fetch API provides a JavaScript interface for accessing and manipulating parts of the HTTP pipeline, such as requests and responses. It also provides a global fetch() method that provides an easy, logical way to fetch resources asynchronously across the network.

This kind of functionality was previously achieved using XMLHttpRequest. Fetch provides a better alternative that can be easily used by other technologies such as Service Workers. Fetch also provides a single logical place to define other HTTP-related concepts such as CORS and extensions to HTTP.

The fetch specification differs from jQuery.ajax() in three main ways:

  • The Promise returned from fetch() won’t reject on HTTP error status even if the response is an HTTP 404 or 500. Instead, it will resolve normally (with ok status set to false), and it will only reject on network failure or if anything prevented the request from completing.
  • fetch() can receive cross-site cookies; you can establish a cross site session using fetch.
  • fetch won’t send cookies, unless you set the credentials init option. (Since Aug 25, 2017. The spec changed the default credentials policy to same-origin. Firefox changed since 61.0b13.)
basic fetch
1
2
3
fetch('http://example.com/movies.json')
.then(response => response.json())
.then(data => console.log(data));

Supplying request options

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Example POST method implementation:
async function postData(url = '', data = {}) {
// Default options are marked with *
const response = await fetch(url, {
method: 'POST', // *GET, POST, PUT, DELETE, etc.
mode: 'cors', // no-cors, *cors, same-origin
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'same-origin', // include, *same-origin, omit
headers: {
'Content-Type': 'application/json'
// 'Content-Type': 'application/x-www-form-urlencoded',
},
redirect: 'follow', // manual, *follow, error
referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
body: JSON.stringify(data) // body data type must match "Content-Type" header
});
return response.json(); // parses JSON response into native JavaScript objects
}

postData('https://example.com/answer', { answer: 42 })
.then(data => {
console.log(data); // JSON data parsed by `data.json()` call
});

Uploading a file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const formData = new FormData();
const fileField = document.querySelector('input[type="file"]');

formData.append('username', 'abc123');
formData.append('avatar', fileField.files[0]);

fetch('https://example.com/profile/avatar', {
method: 'PUT',
body: formData
})
.then(response => response.json())
.then(result => {
console.log('Success:', result);
})
.catch(error => {
console.error('Error:', error);
});

Uploading multiple files

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const formData = new FormData();
const photos = document.querySelector('input[type="file"][multiple]');

formData.append('title', 'My Vegas Vacation');
for (let i = 0; i < photos.files.length; i++) {
formData.append('photos', photos.files[i]);
}

fetch('https://example.com/posts', {
method: 'POST',
body: formData,
})
.then(response => response.json())
.then(result => {
console.log('Success:', result);
})
.catch(error => {
console.error('Error:', error);
});

Processing a text file line by line

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
async function* makeTextFileLineIterator(fileURL) {
const utf8Decoder = new TextDecoder('utf-8');
const response = await fetch(fileURL);
const reader = response.body.getReader();
let { value: chunk, done: readerDone } = await reader.read();
chunk = chunk ? utf8Decoder.decode(chunk) : '';

const re = /\n|\r|\r\n/gm;
let startIndex = 0;
let result;

for (;;) {
let result = re.exec(chunk);
if (!result) {
if (readerDone) {
break;
}
let remainder = chunk.substr(startIndex);
({ value: chunk, done: readerDone } = await reader.read());
chunk = remainder + (chunk ? utf8Decoder.decode(chunk) : '');
startIndex = re.lastIndex = 0;
continue;
}
yield chunk.substring(startIndex, result.index);
startIndex = re.lastIndex;
}
if (startIndex < chunk.length) {
// last line didn't end in a newline char
yield chunk.substr(startIndex);
}
}

async function run() {
for await (let line of makeTextFileLineIterator(urlOfFile)) {
processLine(line);
}
}

run();

Checking that the fetch was successful

1
2
3
4
5
6
7
8
9
10
11
12
13
fetch('flowers.jpg')
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.blob();
})
.then(myBlob => {
myImage.src = URL.createObjectURL(myBlob);
})
.catch(error => {
console.error('There has been a problem with your fetch operation:', error);
});

Supplying your own request object

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const myHeaders = new Headers();

const myRequest = new Request('flowers.jpg', {
method: 'GET',
headers: myHeaders,
mode: 'cors',
cache: 'default',
});

fetch(myRequest)
.then(response => response.blob())
.then(myBlob => {
myImage.src = URL.createObjectURL(myBlob);
});

Request() accepts exactly the same parameters as the fetch() method. You can even pass in an existing request object to create a copy of it:

1
const anotherRequest = new Request(myRequest, myInit);

Headers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const content = 'Hello World';
const myHeaders = new Headers();
myHeaders.append('Content-Type', 'text/plain');
myHeaders.append('Content-Length', content.length.toString());
myHeaders.append('X-Custom-Header', 'ProcessThisImmediately');

const myHeaders = new Headers({
'Content-Type': 'text/plain',
'Content-Length': content.length.toString(),
'X-Custom-Header': 'ProcessThisImmediately'
});

# The contents can be queried and retrieved:
console.log(myHeaders.has('Content-Type')); // true
console.log(myHeaders.has('Set-Cookie')); // false
myHeaders.set('Content-Type', 'text/html');
myHeaders.append('X-Custom-Header', 'AnotherValue');

console.log(myHeaders.get('Content-Length')); // 11
console.log(myHeaders.get('X-Custom-Header')); // ['ProcessThisImmediately', 'AnotherValue']

myHeaders.delete('X-Custom-Header');
console.log(myHeaders.get('X-Custom-Header')); // [ ]
1
2
3
4
5
6
7
8
9
10
11
12
fetch(myRequest)
.then(response => {
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
throw new TypeError("Oops, we haven't got JSON!");
}
return response.json();
})
.then(data => {
/* process your data further */
})
.catch(error => console.error(error));

Guard

Since headers can be sent in requests and received in responses, and have various limitations about what information can and should be mutable, headers objects have a guard property. This is not exposed to the Web, but it affects which mutation operations are allowed on the headers object.

Possible guard values are:

  • none: default.
  • request: guard for a headers object obtained from a request (Request.headers).
  • request-no-cors: guard for a headers object obtained from a request created with Request.mode no-cors.
  • response: guard for a Headers obtained from a response (Response.headers).
  • immutable: Mostly used for ServiceWorkers; renders a headers object read-only.

Note: You may not append or set a request guarded Headers’ Content-Length header. Similarly, inserting Set-Cookie into a response header is not allowed: ServiceWorkers are not allowed to set cookies via synthesized responses.

Response objects

As you have seen above, Response instances are returned when fetch() promises are resolved.

The most common response properties you’ll use are:

  • Response.status — An integer (default value 200) containing the response status code.
  • Response.statusText — A string (default value “”), which corresponds to the HTTP status code message. Note that HTTP/2 does not support status messages.
  • Response.ok — seen in use above, this is a shorthand for checking that status is in the range 200-299 inclusive. This returns a Boolean.

They can also be created programmatically via JavaScript, but this is only really useful in ServiceWorkers, when you are providing a custom response to a received request using a respondWith() method:

1
2
3
4
5
6
7
8
9
10
const myBody = new Blob();

addEventListener('fetch', function(event) {
// ServiceWorker intercepting a fetch
event.respondWith(
new Response(myBody, {
headers: { 'Content-Type': 'text/plain' }
})
);
});

Body

Both requests and responses may contain body data. A body is an instance of any of the following types:

The Body mixin defines the following methods to extract a body (implemented by both Request and Response). These all return a promise that is eventually resolved with the actual content.

This makes usage of non-textual data much easier than it was with XHR.

Request bodies can be set by passing body parameters:

1
2
3
4
5
const form = new FormData(document.getElementById('login-form'));
fetch('/login', {
method: 'POST',
body: form
});

Feature detection

1
2
3
4
5
if (window.fetch) {
// run my fetch request here
} else {
// do something with XMLHttpRequest?
}
Reference
  1. https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch