This is a quick example of how to handle both network errors and HTTP errors (4xx or 5xx) for Fetch API (fetch()
) requests in a single catch()
block.
GET request with error handling
This sends an HTTP GET request to an invalid URL on the Reqres api which is a fake online REST api used for testing, then writes the error message to the parent of the #get-request-error-handling .total
element and logs the error to the console.
Error Handling
The fetch()
function will automatically throw an error for network errors but not for HTTP errors such as 4xx or 5xx responses. For HTTP errors we can check the response.ok
property to see if the request failed and reject the promise ourselves by calling return Promise.reject(error);
. This approach means that both types of failed requests — network errors and http errors — can be handled by a single catch()
block instead of needing two separate pieces of error handling code.
JSON Check
The fetch .then()
callback is passed the HTTP response
object when the request is completed, the function checks if the response type is JSON before parsing the response body with the response.json()
method, because calling response.json()
will cause an error if the response doesn’t contain JSON data.
// Fetch GET request with error handling
const element = document.querySelector('#get-request .result');
fetch('https://reqres.in/invalid-url')
.then(async response => {
const isJson = response.headers.get('content-type')?.includes('application/json');
const data = isJson ? await response.json() : null;
// check for error response
if (!response.ok) {
// get error message from body or default to response status
const error = (data && data.message) || response.status;
return Promise.reject(error);
}
element.innerHTML = JSON.stringify(data, null, 4);
})
.catch(error => {
element.parentElement.innerHTML = `Error: ${error}`;
console.error('There was an error!', error);
});
Run the example Fetch GET request on StackBlitz at https://stackblitz.com/edit/fetch-error-handling-http-and-network-errors
Subscribe or Follow Me For Updates
Subscribe to my YouTube channel or follow me on Twitter, Facebook or GitHub to be notified when I post new content.
- Subscribe on YouTube at https://www.youtube.com/JasonWatmore
- Follow me on Twitter at https://twitter.com/jason_watmore
- Follow me on Facebook at https://www.facebook.com/JasonWatmoreBlog
- Follow me on GitHub at https://github.com/cornflourblue
-
Feed formats available:
RSS,
Atom,
JSON
Other than coding…
I’m currently attempting to travel around Australia by motorcycle with my wife Tina on a pair of Royal Enfield Himalayans. You can follow our adventures on YouTube, Instagram and Facebook.
- Subscribe on YouTube at https://www.youtube.com/TinaAndJason
- Follow us on Instagram at https://www.instagram.com/tinaandjason
- Follow us on Facebook at https://www.facebook.com/TinaAndJasonVlog
- Visit our website at https://tinaandjason.com.au
Need Some Fetch Help?
Search fiverr to find help quickly from experienced Fetch developers.
This documents the polyfillable parts of the
WHATWG Fetch standard.
See Caveats for notable exceptions.
Usage synopsis (use the argument links to find out more):
fetch(url, options).then(function(response) {
// handle HTTP response
}, function(error) {
// handle network error
})
More comprehensive usage example:
fetch(url, {
method: "POST",
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json"
},
credentials: "same-origin"
}).then(function(response) {
response.status //=> number 100–599
response.statusText //=> String
response.headers //=> Headers
response.url //=> String
return response.text()
}, function(error) {
error.message //=> String
})
Request
Synopsis:
new Request(url, options)
Request represents a HTTP request to be performed via fetch()
.
Typically a Request doesn’t need to be constructed manually,
as it’s instantiated internally when fetch()
is called.
URL (Request or string)
The URL of the resource which is being fetched.
Typically this is an absolute URL without the host component, e.g. "/path"
.
If the URL has the host of another site, the request is performed in accordance to
CORS.
Any non-Request object will be converted to a String, making it possible to
pass an instance of URL,
for example.
A request can be initialized using another instance of Request in place of the URL.
In that case, the URL and other options are inherited from the provided Request object.
Options
method
(String) — HTTP request method. Default:"GET"
body
(String, body types) — HTTP request bodyheaders
(Object, Headers) — Default:{}
credentials
(String) — Authentication credentials mode. Default:"omit"
"omit"
— don’t include authentication credentials (e.g. cookies) in the request"same-origin"
— include credentials in requests to the same site"include"
— include credentials in requests to all sites
Body types
Class | Default Content-Type |
---|---|
String | text/plain;charset=UTF-8 |
URLSearchParams | application/x-www-form-urlencoded;charset=UTF-8 |
FormData | multipart/form-data |
Blob | inherited from the blob.type property |
ArrayBuffer | |
TypedArray | |
DataView |
Other data structures need to be encoded beforehand as one of the above types.
For instance, JSON.stringify(data)
can be used to serialize a
data structure into a JSON string.
Note that HTTP servers often require that requests that are posted with a body
also specify their type via a Content-Type
request header.
Response
Response represents a HTTP response from the server.
Typically a Response is not constructed manually,
but is available as argument to the resolved promise callback.
Properties
status
(number) — HTTP response code in the 100–599 rangestatusText
(String) — Status text as reported by the server, e.g. «Unauthorized»ok
(boolean) — True ifstatus
is HTTP 2xxheaders
(Headers)url
(String)
Body methods
Each of the methods to access the response body returns a Promise that will be
resolved when the associated data type is ready.
text()
— yields the response text as Stringjson()
— yields the result ofJSON.parse(responseText)
blob()
— yields a BlobarrayBuffer()
— yields an ArrayBufferformData()
— yields FormData that can be forwarded to another request
Other response methods
clone()
Response.error()
Response.redirect()
Synopsis:
new Headers(hash)
Headers represents a set of request/response HTTP headers.
It allows for case-insensitive lookup of header by name,
as well as merging multiple values of a single header.
Methods
has(name)
(boolean)get(name)
(String)set(name, value)
append(name, value)
delete(name)
forEach(function(value, name){ ... }, [thisContext])
Error
If there is a network error or another reason why the HTTP request couldn’t be fulfilled,
the fetch()
promise will be rejected with a reference to that error.
Note that the promise won’t be rejected in case of HTTP 4xx or 5xx server responses.
The promise will be resolved just as it would be for HTTP 2xx.
Inspect the response.status
number within the resolved callback to add conditional handling of server errors to your code.
fetch(...).then(function(response) {
if (response.ok) {
return response
} else {
var error = new Error(response.statusText)
error.response = response
throw error
}
})
Caveats
The whatwg-fetch polyfill isn’t
able nor does it aim to implement the entire WHATWG Fetch standard, since some
of the standard features would be non-trivial or otherwise unfeasible to
implement. Notable examples include:
- Inability to set the redirect mode
- Inability to change the cache directive
- Inability to disable same-origin cookies
The Fetch API provides a JavaScript interface for accessing and manipulating parts of the protocol, 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 the following significant ways:
- The Promise returned from
fetch()
won’t reject on HTTP error status even if the response is an HTTP 404 or 500. Instead, as soon as the server responds with headers, the Promise will resolve normally (with theok
property of the response set to false if the response isn’t in the range 200–299), and it will only reject on network failure or if anything prevented the request from completing. - Unless
fetch()
is called with thecredentials
option set toinclude
,fetch()
:- won’t send cookies in cross-origin requests
- won’t set any cookies sent back in cross-origin responses
- As of August 2018, the default credentials policy changed to same-origin. Firefox was also modified in version 61.0b13)
A basic fetch request is really simple to set up. Have a look at the following code:
fetch('http://example.com/movies.json')
.then((response) => response.json())
.then((data) => console.log(data));
Here we are fetching a JSON file across the network and printing it to the console. The simplest use of fetch()
takes one argument — the path to the resource you want to fetch — and does not directly return the JSON response body but instead returns a promise that resolves with a Response
object.
The Response
object, in turn, does not directly contain the actual JSON response body but is instead a representation of the entire HTTP response. So, to extract the JSON body content from the Response
object, we use the json()
method, which returns a second promise that resolves with the result of parsing the response body text as JSON.
Note: See the Body section for similar methods to extract other types of body content.
Fetch requests are controlled by the connect-src
directive of Content Security Policy rather than the directive of the resources it’s retrieving.
Supplying request options
The fetch()
method can optionally accept a second parameter, an init
object that allows you to control a number of different settings:
See fetch()
for the full options available, and more details.
// 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
});
Note that mode: "no-cors"
only allows a limited set of headers in the request:
Accept
Accept-Language
Content-Language
Content-Type
with a value ofapplication/x-www-form-urlencoded
,multipart/form-data
, ortext/plain
Sending a request with credentials included
To cause browsers to send a request with credentials included on both same-origin and cross-origin calls, add credentials: 'include'
to the init
object you pass to the fetch()
method.
fetch('https://example.com', {
credentials: 'include'
});
Note: Access-Control-Allow-Origin
is prohibited from using a wildcard for requests with credentials: 'include'
. In such cases, the exact origin must be provided; even if you are using a CORS unblocker extension, the requests will still fail.
Note: Browsers should not send credentials in preflight requests irrespective of this setting. For more information see: CORS > Requests with credentials.
If you only want to send credentials if the request URL is on the same origin as the calling script, add credentials: 'same-origin'
.
// The calling script is on the origin 'https://example.com'
fetch('https://example.com', {
credentials: 'same-origin'
});
To instead ensure browsers don’t include credentials in the request, use credentials: 'omit'
.
fetch('https://example.com', {
credentials: 'omit'
})
Uploading JSON data
Use fetch()
to POST JSON-encoded data.
const data = { username: 'example' };
fetch('https://example.com/profile', {
method: 'POST', // or 'PUT'
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
.then((response) => response.json())
.then((data) => {
console.log('Success:', data);
})
.catch((error) => {
console.error('Error:', error);
});
Uploading a file
Files can be uploaded using an HTML <input type="file" />
input element, FormData()
and fetch()
.
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
Files can be uploaded using an HTML <input type="file" multiple />
input element, FormData()
and fetch()
.
const formData = new FormData();
const photos = document.querySelector('input[type="file"][multiple]');
formData.append('title', 'My Vegas Vacation');
let i = 0;
for (const photo of photos.files) {
formData.append(`photos_${i}`, photo);
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
The chunks that are read from a response are not broken neatly at line boundaries and are Uint8Arrays, not strings. If you want to fetch a text file and process it line by line, it is up to you to handle these complications. The following example shows one way to do this by creating a line iterator (for simplicity, it assumes the text is UTF-8, and doesn’t handle fetch errors).
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|rn/gm;
let startIndex = 0;
let result;
while (true) {
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 (const line of makeTextFileLineIterator(urlOfFile)) {
processLine(line);
}
}
run();
Checking that the fetch was successful
A fetch()
promise will reject with a TypeError
when a network error is encountered or CORS is misconfigured on the server-side, although this usually means permission issues or similar — a 404 does not constitute a network error, for example. An accurate check for a successful fetch()
would include checking that the promise resolved, then checking that the Response.ok
property has a value of true. The code would look something like this:
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
Instead of passing a path to the resource you want to request into the fetch()
call, you can create a request object using the Request()
constructor, and pass that in as a fetch()
method argument:
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:
const anotherRequest = new Request(myRequest, myInit);
This is pretty useful, as request and response bodies can only be used once.
Making a copy like this allows you to effectively use the request/response again while varying the init
options if desired.
The copy must be made before the body is read.
Note: There is also a clone()
method that creates a copy. Both methods of creating a copy will fail if the body of the original request or response has already been read, but reading the body of a cloned response or request will not cause it to be marked as read in the original.
The Headers
interface allows you to create your own headers object via the Headers()
constructor. A headers object is a simple multi-map of names to values:
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');
The same can be achieved by passing an array of arrays or an object literal to the constructor:
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')); // null
Some of these operations are only useful in ServiceWorkers
, but they provide a much nicer API for manipulating headers.
All of the Headers methods throw a TypeError
if a header name is used that is not a valid HTTP Header name. The mutation operations will throw a TypeError
if there is an immutable guard (see below). Otherwise, they fail silently. For example:
const myResponse = Response.error();
try {
myResponse.headers.set('Origin', 'http://mybank.com');
} catch (e) {
console.log('Cannot pretend to be a bank!');
}
A good use case for headers is checking whether the content type is correct before you process it further. For example:
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 withRequest.mode
no-cors
.response
: guard for a headers object obtained from a response (Response.headers
).immutable
: guard that renders a headers object read-only; mostly used for ServiceWorkers.
Note: You may not append or set the Content-Length
header on a guarded headers object for a response
. 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 value.
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:
const myBody = new Blob();
addEventListener('fetch', (event) => {
// ServiceWorker intercepting a fetch
event.respondWith(
new Response(myBody, {
headers: { 'Content-Type': 'text/plain' }
})
);
});
The Response()
constructor takes two optional arguments — a body for the response, and an init object (similar to the one that Request()
accepts.)
Note: The static method error()
returns an error response. Similarly, redirect()
returns a response resulting in a redirect to a specified URL. These are also only relevant to Service Workers.
Body
Both requests and responses may contain body data. A body is an instance of any of the following types:
ArrayBuffer
TypedArray
(Uint8Array and friends)DataView
Blob
File
String
, or a string literalURLSearchParams
FormData
The Request
and Response
interfaces share the following methods to extract a body. These all return a promise that is eventually resolved with the actual content.
Request.arrayBuffer()
/Response.arrayBuffer()
Request.blob()
/Response.blob()
Request.formData()
/Response.formData()
Request.json()
/Response.json()
Request.text()
/Response.text()
This makes usage of non-textual data much easier than it was with XHR.
Request bodies can be set by passing body parameters:
const form = new FormData(document.getElementById('login-form'));
fetch('/login', {
method: 'POST',
body: form
});
Both request and response (and by extension the fetch()
function), will try to intelligently determine the content type. A request will also automatically set a Content-Type
header if none is set in the dictionary.
Feature detection
Fetch API support can be detected by checking for the existence of Headers
, Request
, Response
or fetch()
on the Window
or Worker
scope. For example:
if (window.fetch) {
// run my fetch request here
} else {
// do something with XMLHttpRequest?
}
Specifications
Specification |
---|
Fetch Standard # fetch-method |
Browser compatibility
BCD tables only load in the browser
See also
Время прочтения
5 мин
Просмотры 269K
Прощай, XMLHttpRequest!
fetch()
позволяет вам делать запросы, схожие с XMLHttpRequest (XHR). Основное отличие заключается в том, что Fetch API использует Promises (Обещания), которые позволяют использовать более простое и чистое API, избегать катастрофического количества callback’ов и необходимости помнить API для XMLHttpRequest.
Fetch API стал доступен пользователям вместе с Service Worker’ами в global скоупе в Chrome 40, однако уже в версии 42 он станет доступен в скоупе window. Разумеется, для всех остальных браузеров, которые пока ещё не поддерживают fetch существует полифил от GitHub, который доступен уже сегодня.
Простой Fetch запрос
Давайте начнём со сравнения простого примера, реализованного с XMLHttpRequest
и fetch
. Всё, что мы будем делать в этом примере — сделаем запрос на URL, получим ответ и распарсим его как JSON.
XMLHttpRequest
Пример с XMLHttpRequest потребует от нас установить два обработчика событий на success
и error
, а так же вызвать два метода: open()
и send()
. Пример из MDN документации:
function reqListener() {
var data = JSON.parse(this.responseText);
console.log(data);
}
function reqError(err) {
console.log('Fetch Error :-S', err);
}
var oReq = new XMLHttpRequest();
oReq.onload = reqListener;
oReq.onerror = reqError;
oReq.open('get', './api/some.json', true);
oReq.send();
Fetch
Наш fetch запрос будет выглядеть так:
fetch('./api/some.json')
.then(
function(response) {
if (response.status !== 200) {
console.log('Looks like there was a problem. Status Code: ' +
response.status);
return;
}
// Examine the text in the response
response.json().then(function(data) {
console.log(data);
});
}
)
.catch(function(err) {
console.log('Fetch Error :-S', err);
});
Первым делом мы проверяем статус ответа и проверяем, успешно ли выполнился запрос (ожидаем 200 статус). Если всё хорошо, то парсим ответ как JSON.
Ответом fetch()
является Stream-объект. Это означает, что в результате вызова метода json()
мы получим Promise, т.к. чтение из подобного объекта является асинхронным.
Метаданные ответа
В предыдущем примере мы изучили, как можно проверить статус объекта ответа и конвентировать сам ответ в JSON. Остальные метаданные, к которым вы возможно получить доступ (например, заголовки), приведены ниже:
fetch('users.json').then(function(response) {
console.log(response.headers.get('Content-Type'));
console.log(response.headers.get('Date'));
console.log(response.status);
console.log(response.statusText);
console.log(response.type);
console.log(response.url);
});
Типы ответа
Когда мы делаем fetch-запрос, ответу будет дан тип «basic», «cors» или «opaque». Эти «типы» указывают на то, с какого ресурса пришли данные и могут быть использованы для того, чтобы определить процесс обработки данных.
Когда запрос сделан на ресурс, находящимся на том же origin (имеется ввиду, что запрос выполняется в рамках одного сайта. прим. пер.), ответ будет содержать тип «базовый» и для такого запроса не будет никаких ограничений.
Если запрос сделан с одного origin’а на другой (кроссдоменный запрос), который, в свою очередь, вернул CORS заголовки, тогда типом будет являться «cors». Объекты с типами «cors» и «basic» почти идентичны, однако «cors» несколько ограничивает метаданные, к которым может быть получен доступ до «Cache-Control», «Content-Language», «Content-Type», «Expires», «Last-Modified», и «Pragma».
Что касается «opaque» — то он приходит в случаях, когда выполняется CORS запрос, но удаленный ресурс не возвращает CORS заголовки. Данный тип запроса не предоставляет доступ данным или заголовку статуса, поэтому мы не имеем возможности судить о результате выполнения запроса. В рамках текущей имплементации fetch()
не представляется возможности выполнять CORS запросы из скоупа window, и вот здесь написано почему. Эта функциональность должна быть добавлена, как только Cache API станет доступным из объекта window.
Вы можете определить ожидаемый режим запроса, тем самым фильтруя результаты запросов с неподходящим типом. Режим запроса может быть установлен на следующий:
— “same-origin” успешно выполняется только для запросов на тот же самый origin, все остальные запросы будут отклонены.
— “cors” работает так же, как «same-origin» + добавляет возможность создавать запросы к сторонним сайтам, если они возвращают соответствующие CORS заголовки.
— “cors-with-forced-preflight” работает так же, как «cors», но перед запросом всегда отсылает тестовый запрос на проверку.
— “no-cors” используется, когда необходимо выполнить запрос к origin, который не отсылает CORS заголовки и результатом выполнения является объект с типом «opaque». Как говорилось выше, в данный момент это невозможно в скоупе window.
Чтобы определить режим запроса, добавьте объект опций вторым параметром к запросу и установите «mode» в этом объекте:
fetch('http://some-site.com/cors-enabled/some.json', {mode: 'cors'})
.then(function(response) {
return response.text();
})
.then(function(text) {
console.log('Request successful', text);
})
.catch(function(error) {
log('Request failed', error)
});
Цепочки Promises
Одной из прекрасных особенностей Promise’ов является возможность группировать их в цепочки. Если говорить о них в скоупе fetch()
, то они позволяют нам «шарить» логику между запросами.
Если вы работаете с JSON API, вам потребуется проверить статус и распарсить JSON для каждого ответа. Вы можете упростить свой код, определив парсинг статуса и JSON как раздельные функции, которые вернут Promise’ы. Вам останется подумать только об обработке самих данных и, разумеется, исключений.
function status(response) {
if (response.status >= 200 && response.status < 300) {
return Promise.resolve(response)
} else {
return Promise.reject(new Error(response.statusText))
}
}
function json(response) {
return response.json()
}
fetch('users.json')
.then(status)
.then(json)
.then(function(data) {
console.log('Request succeeded with JSON response', data);
}).catch(function(error) {
console.log('Request failed', error);
});
Мы определяем функцию, которая проверяет response.status
и возвращает результат: Promise.resolve()
или Promise.reject()
. Это первый вызванный метод в нашей цепочке, и если он успешно завершается(Promise.resolve()
), то вызывается следующий за ним метод — fetch()
, который, в свою очередь, опять возвращает Promise от response.json()
. После этого вызова, в случае удачного выполнения, у нас будет готовый JSON объект. Если парсинг провалится, Promise будет отменен и сработает условие возникновения исключения.
Но самое лучшее здесь — это возможность переиспользовать такой код для всех fetch-запросов в приложении. Такой код проще поддерживать, читать и тестировать.
POST запрос
Уже давно никого не удивишь необходимостью использовать POST метод с передачей параметров в «теле» запроса для работы с API.
Чтобы осуществить такой запрос, мы должны указать соответствующие параметры в объекте настроек fetch()
:
fetch(url, {
method: 'post',
headers: {
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8"
},
body: 'foo=bar&lorem=ipsum'
})
.then(json)
.then(function (data) {
console.log('Request succeeded with JSON response', data);
})
.catch(function (error) {
console.log('Request failed', error);
});
Посылаем учётные данные через Fetch-запрос
Если вы хотите отправить запрос с каким-либо учётными данными (например, с cookie), вам следует установить `credentials` в опциях запроса на «include»:
fetch(url, {
credentials: 'include'
})
FAQ
Могу ли я отменить fetch()
запрос?
В настоящий момент это невозможно, но это активно обсуждается на GitHub
Существует ли полифил?
Да
Почему «no-cors» реализован для service workers, но не для window?
Это было сделано из соображений безопасности. Подробнее можно ознакомиться здесь.
Quiz: What does this call to the web’s new fetch()
API do?
fetch("http://httpstat.us/500")
.then(function() {
console.log("ok");
}).catch(function() {
console.log("error");
});
If you’re like me, you might assume this code logs “error” when run—but it actually logs “ok”.
This expectation probably comes from years of jQuery development, as jQuery’s ajax()
method invokes its fail
handler when the response contains a failed HTTP status code. For example, the code below logs “error” when run:
$.ajax("http://httpstat.us/500")
.done(function() {
console.log("ok");
}).fail(function() {
console.log("error");
});
Why does fetch()
work this way?
Per MDN, the fetch()
API only rejects a promise when a “network error is encountered, although this usually means permissions issues or similar.” Basically fetch()
will only reject a promise if the user is offline, or some unlikely networking error occurs, such a DNS lookup failure.
The good is news is fetch
provides a simple ok
flag that indicates whether an HTTP response’s status code is in the successful range or not. For instance the following code logs “Error: Internal Server Error(…)”:
fetch("http://httpstat.us/500")
.then(function(response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}).then(function(response) {
console.log("ok");
}).catch(function(error) {
console.log(error);
});
To keep this code DRY and reusable, you probably want to create a generic error handling function you can use for all of your fetch()
calls. The following code refactors the error handling into a handleErrors()
function:
function handleErrors(response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
fetch("http://httpstat.us/500")
.then(handleErrors)
.then(function(response) {
console.log("ok");
}).catch(function(error) {
console.log(error);
});
For added fun you can use ES6 arrow functions to make the callback formatting a little less verbose:
function handleErrors(response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
fetch("http://httpstat.us/500")
.then(handleErrors)
.then(response => console.log("ok") )
.catch(error => console.log(error) );
Parting thoughts
Although I still don’t like fetch()
’s lack of rejecting failed HTTP status codes, over time fetch()
’s behavior has grown on me—mostly because it gives me more control over how I handle individual problems. Plus, the composable nature of fetch()
makes it fairly trivial to manually handle errors without adding a bunch of verbose code.
Overall I think it’s worth taking few minutes to play with fetch()
, even if it’s just to see what you think. It’s certainly a far more readable alternative to XMLHttpRequest. If you happen to be building NativeScript apps, you might not know that you can use fetch()
today without any need for a polyfill or fallback. And something about using fetch()
to perform HTTP requests in native Android and iOS apps is just plain cool
This article was updated on September 15th, 2015 to use a simpler handleErrors()
function based on a comment from Jake Archibald.
The Fetch API is a promise-based JavaScript API for making asynchronous HTTP requests in the browser similar to XMLHttpRequest (XHR). Unlike XHR, it is a simple and clean API that uses promises to provide a powerful and flexible feature set to fetch resources from the server.
Fetch API is pretty much standardized and is supported by all modern browsers except IE. If you need all browsers, including IE, add a polyfill released by GitHub to your project.
How to use Fetch API?
Using Fetch API is really simple. Just pass the URL, the path to the resource you want to fetch, to the fetch()
method:
fetch('/js/users.json')
.then(response => {
// handle response data
})
.catch(err => {
// handle errors
})
We pass the path for the resource we want to retrieve as a parameter to fetch()
. It returns a promise that sends the response to then()
when it is fulfilled. The catch()
method intercepts errors if the request fails to complete due to network failure or other reasons.
Make GET request using Fetch API
By default, the Fetch API uses the GET method for asynchronous requests. Let’s use the Reqres REST API to retrieve a list of users using a GET request:
fetch('https://reqres.in/api/users')
.then(res => res.json())
.then(res => {
res.data.map(user => {
console.log(`${user.id}: ${user.first_name} ${user.last_name}`)
})
})
The above request prints the following on the console:
1: George Bluth
2: Janet Weaver
3: Emma Wong
Calling the fetch()
method returns a promise. The response returned by the promise is a stream object. When we call the json()
method on the stream object, it returns another promise. The call to the json()
method indicates that we are expecting a JSON response. For an XML response, you should use the text()
method.
Make POST request using Fetch API
Just like Axios, Fetch also allows us to use any other HTTP method in the request: POST, PUT, DELETE, HEAD, and OPTIONS. All you need to do is set the method
and body
parameters in the fetch()
options:
const user = {
first_name: 'John',
last_name: 'Lilly',
job_title: 'Software Engineer'
}
const options = {
method: 'POST',
body: JSON.stringify(user),
headers: {
'Content-Type': 'application/json'
}
}
fetch('https://reqres.in/api/users', options)
.then(res => res.json())
.then(res => console.log(res))
The Reqres API returns the body data back with an ID and created timestamp attached:
{
"first_name":"John",
"last_name":"Lilly",
"job_title":"Software Engineer",
"id":"482",
"createdAt":"2019-05-12T15:09:13.140Z"
}
Make DELETE request using Fetch API
The DELETE request looks very similar to the POST request, except body
is not required:
const options = {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
}
}
fetch('https://reqres.in/api/users/2', options)
.then(res => {
if (res.ok) {
return Promise.resolve('User deleted.')
} else {
return Promise.reject('An error occurred.')
}
})
.then(res => console.log(res))
Error handling in Fetch API
Since the fetch()
method returns a promise, error handling is easy. We can use the catch()
method of the promise object to intercept any error thrown during the execution of the request.
However, no error will be thrown if the request hits the server and comes back, regardless of the server’s response. The promise returned by fetch()
does not reject HTTP errors, even if the HTTP response code is 404 or 500.
Fortunately, you can use the ok
property of the response object to check whether the request was successful or not:
fetch('https://reqres.in/api/users/22') // 404 Error
.then(res => {
if (res.ok) {
return res.json()
} else {
return Promise.reject(res.status)
}
})
.then(res => console.log(res))
.catch(err => console.log(`Error with message: ${err}`))
Request headers (like Accept
, Content-Type
, User-Agent
, Referer
, etc.) are essential for any HTTP request. The Fetch API’s Headers
object allows us to set, remove, or retrieve HTTP request headers.
We can create a header object using the Headers()
constructor and then use the append
, has
, get
, set
, and delete
methods to modify request headers:
// create an empty `Headers` object
const headers = new Headers()
// add headers
headers.append('Content-Type', 'text/plain')
headers.append('Accept', 'application/json')
// add custom headers
headers.append('X-AT-Platform', 'Desktop')
headers.append('X-AT-Source', 'Google Search')
// check if the header exists
headers.has('Accept') // true
// get headers
headers.get('Accept') // application/json
headers.get('X-AT-Source') // Google Search
// update header value
headers.set('Content-Type', 'application/json')
// remove headers
headers.delete('Content-Type')
headers.delete('X-AT-Platform')
We can also pass an array of arrays or an object literal to the constructor to create a header object:
// passing an object literal
const headers = new Headers({
'Content-Type': 'application/json',
Accept: 'application/json'
})
// OR
// passing an array of arrays
const headers = new Headers([
['Content-Type', 'application/json'],
['Accept', 'application/json']
])
To add headers to the request, create a Request
instance and pass it to the fetch()
method instead of the URL:
const request = new Request('https://reqres.in/api/users', {
headers: headers
})
fetch(request)
.then(res => res.json())
.then(json => console.log(json))
.catch(err => console.error('Error:', err))
Fetch API request object
The Request
object represents a resource request and can be created by calling the Request()
constructor:
const request = new Request('https://reqres.in/api/users')
The Request
object also accepts a URL object:
const url = new URL('https://reqres.in/api/users')
const request = new Request(url)
By passing a Request
object to fetch()
, you can easily customize the request properties:
method
— An HTTP method likeGET
,POST
,PUT
,DELETE
,HEAD
url
— The URL to the request, a string or a URL objectheaders
— aHeaders
object for request headersreferrer
— referrer of the request (e.g.,client
)mode
— The mode for cross-origin requests (e.g.,cors
,no-cors
,same-origin
)credentials
— Should cookies and HTTP-Authorization headers go with the request? (e.g.,include
,omit
,same-origin
)redirect
— The redirect mode of the request (e.g.,follow
,error
,manual
)integrity
— The subresource integrity value of the requestcache
— The cache mode of the request (e.g,default
,reload
,no-cache
)
Let us create a Request
object with some customized properties and body content to make a POST request:
const user = {
first_name: 'John',
last_name: 'Lilly',
job_title: 'Software Engineer'
}
const headers = new Headers({
'Content-Type': 'application/json',
Accept: 'application/json'
})
const request = new Request('https://reqres.in/api/users', {
method: 'POST',
headers: headers,
redirect: 'follow',
mode: 'cors',
body: JSON.stringify(user)
})
fetch(request)
.then(res => res.json())
.then(json => console.log(json))
.catch(err => console.error('Error:', err))
Only the first argument, the URL, is required. All these properties are read-only. You can not change their value once the request object is created. The Fetch API does not strictly require a Request
object. The object literal, which we pass to the fetch()
method, acts like a Request
object:
fetch('https://reqres.in/api/users', {
method: 'POST',
headers: headers,
redirect: 'follow',
mode: 'cors',
body: JSON.stringify(user)
})
.then(res => res.json())
.then(json => console.log(json))
.catch(err => console.error('Error:', err))
Fetch API response object
The Response
object returned by the fetch()
method contains the information about the request and the response of the network request, including headers, status code, and status message:
fetch('https://reqres.in/api/users').then(res => {
// get response headers
console.log(res.headers.get('content-type))
console.log(res.headers.get('expires'))
// HTTP response status code
console.log(res.status)
// shorthand for `status` between 200 and 299
console.log(res.ok)
// status message of the response e.g. `OK`
console.log(res.statusText)
// check if there was a redirect
console.log(res.redirected)
// get the response type (e.g., `basic`, `cors`)
console.log(res.type)
// the full path of the resource
console.log(res.url)
})
The response body is accessible through the following methods:
json()
returns the body as a JSON objecttext()
returns the body as a stringblob()
returns the body as a Blob objectformData()
returns the body as a FormData objectarrayBuffer()
returns the body as an ArrayBuffer object
All these methods return a promise. Here is an example of text()
method:
fetch('https://reqres.in/api/unknown/2')
.then(res => res.text())
.then(res => console.log(res))
The output of the above network call will be a JSON string:
'{"data":{"id":2,"name":"fuchsia rose","year":2001,"color":"#C74375","pantone_value":"17-2031"}}'
Send cookies using Fetch API
When you use Fetch to get a resource, the request does not contain credentials such as cookies. If you want to send cookies, you have to explicitly enable credentials like the below:
fetch(url, {
credentials: 'include'
})
Use async-await with Fetch API
Since Fetch is a promise-based API, we can go one step further and use the latest ES2017 async/await syntax to make our code even simpler and synchronous-looking:
const fetchUsers = async () => {
try {
const res = await fetch('https://reqres.in/api/users')
if (!res.ok) {
throw new Error(res.status)
}
const data = await res.json()
console.log(data)
} catch (error) {
console.log(error)
}
}
fetchUsers()
Conclusion
That’s all folks for introduction to JavaScript Fetch API. It is a significant improvement over XMLHttpRequest
with a simple, elegant, and easy-to-use interface. Fetch works great for fetching network resources (even across the network inside the service workers).
The Fetch API is supported by all modern browsers, so there is no need to use any polyfill unless you want to support IE.
Read next: How to make HTTP requests using XHR in JavaScript.
✌️ Like this article? Follow me on
Twitter
and LinkedIn.
You can also subscribe to
RSS Feed.