even though this is a bit old question I’m going to chime in.
In the comments above there was this answer:
const fetchJSON = (...args) => {
return fetch(...args)
.then(res => {
if(res.ok) {
return res.json()
}
return res.text().then(text => {throw new Error(text)})
})
}
Sure, you can use it, but there is one important thing to bare in mind. If you return json from the rest api looking as {error: 'Something went wrong'}, the code return res.text().then(text => {throw new Error(text)}) displayed above will certainly work, but the res.text() actually returns the string. Yeah, you guessed it! Not only will the string contain the value but also the key merged together! This leaves you with nothing but to separate it somehow. Yuck!
Therefore, I propose a different solution.
fetch(`backend.com/login`, {
method: 'POST',
body: JSON.stringify({ email, password })
})
.then(response => {
if (response.ok) return response.json();
return response.json().then(response => {throw new Error(response.error)})
})
.then(response => { ...someAdditional code })
.catch(error => reject(error.message))
So let’s break the code, the first then in particular.
.then(response => {
if (response.ok) return response.json();
return response.json().then(response => {throw new Error(response.error)})
})
If the response is okay (i.e. the server returns 2xx response), it returns another promise response.json() which is processed subsequently in the next then block.
Otherwise, I will AGAIN invoke response.json() method, but will also provide it with its own then block of code. There I will throw a new error. In this case, the response in the brackets throw new Error(response.error) is a standard javascript object and therefore I’ll take the error from it.
As you can see, there is also the catch block of code at the very end, where you process the newly thrown error. (error.message <— the error is an object consisting of many fields such as name or message. I am not using name in this particular instance. You are bound to have this knowledge anyway)
Tadaaa! Hope it helps!
I’ve been looking around this problem and has come across this post so thought that my answer would benefit someone in the future.
Have a lovely day!
Marek
Fetch API предоставляет интерфейс JavaScript для работы с запросами и ответами HTTP. Он также предоставляет глобальный метод fetch() (en-US), который позволяет легко и логично получать ресурсы по сети асинхронно.
Подобная функциональность ранее достигалась с помощью XMLHttpRequest. Fetch представляет собой лучшую альтернативу, которая может быть легко использована другими технологиями, такими как Service Workers (en-US). Fetch также обеспечивает единое логическое место для определения других связанных с HTTP понятий, такие как CORS и расширения для HTTP.
Обратите внимание, fetch спецификация отличается от jQuery.ajax() в основном в двух пунктах:
- Promise возвращаемый вызовом
fetch()не перейдёт в состояние «отклонено» из-за ответа HTTP, который считается ошибкой, даже если ответ HTTP 404 или 500. Вместо этого, он будет выполнен нормально (с значением false в статусеok) и будет отклонён только при сбое сети или если что-то помешало запросу выполниться. - По умолчанию,
fetchне будет отправлять или получать cookie файлы с сервера, в результате чего запросы будут осуществляться без проверки подлинности, что приведёт к неаутентифицированным запросам, если сайт полагается на проверку пользовательской сессии (для отправки cookie файлов в аргументе init options должно быть задано значение свойства credentials отличное от значения по умолчаниюomit).
Примечание: 25 августа 2017 г. в спецификации изменилось значение по умолчанию свойства credentials на same-origin. Firefox применяет это изменение с версии 61.0b13.
Базовый запрос на получение данных действительно прост в настройке. Взгляните на следующий код:
fetch('http://example.com/movies.json')
.then((response) => {
return response.json();
})
.then((data) => {
console.log(data);
});
Здесь мы забираем JSON файл по сети и выводим его содержимое в консоль. Самый простой способ использования fetch() заключается в вызове этой функции с одним аргументом — строкой, содержащей путь к ресурсу, который вы хотите получить — которая возвращает promise, содержащее ответ (объект Response).
Конечно, это просто HTTP-ответ, а не фактический JSON. Чтобы извлечь содержимое тела JSON из ответа, мы используем json() (en-US) метод (определён миксином Body, который реализован в объектах Request и Response.)
Примечание: Миксин Body имеет подобные методы для извлечения других типов контента; см. раздел Тело.
Fetch-запросы контролируются посредством директивы connect-src (Content Security Policy (en-US)), а не директивой извлекаемых ресурсов.
Установка параметров запроса
Метод fetch() может принимать второй параметр — объект init, который позволяет вам контролировать различные настройки:
// Пример отправки POST запроса:
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, *client
body: JSON.stringify(data) // body data type must match "Content-Type" header
});
return await 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 `response.json()` call
});
С подробным описанием функции и полным списком параметров вы можете ознакомиться на странице fetch() (en-US).
Отправка запроса с учётными данными
Чтобы браузеры могли отправлять запрос с учётными данными (даже для cross-origin запросов), добавьте credentials: 'include' в объект init, передаваемый вами в метод fetch():
fetch('https://example.com', {
credentials: 'include'
})
Если вы хотите отправлять запрос с учётными данными только если URL принадлежит одному источнику (origin) что и вызывающий его скрипт, добавьте credentials: ‘same-origin’.
// Вызывающий скрипт принадлежит источнику 'https://example.com'
fetch('https://example.com', {
credentials: 'same-origin'
})
Напротив, чтобы быть уверенным, что учётные данные не передаются с запросом, используйте credentials: ‘omit’:
fetch('https://example.com', {
credentials: 'omit'
})
Отправка данных в формате JSON
При помощи fetch() (en-US) можно отправлять POST-запросы в формате JSON.
const url = 'https://example.com/profile';
const data = { username: 'example' };
try {
const response = await fetch(url, {
method: 'POST', // или 'PUT'
body: JSON.stringify(data), // данные могут быть 'строкой' или {объектом}!
headers: {
'Content-Type': 'application/json'
}
});
const json = await response.json();
console.log('Успех:', JSON.stringify(json));
} catch (error) {
console.error('Ошибка:', error);
}
Загрузка файла на сервер
На сервер можно загрузить файл, используя комбинацию HTML-элемента <input type="file" />, FormData() и fetch().
const formData = new FormData();
const fileField = document.querySelector('input[type="file"]');
formData.append('username', 'abc123');
formData.append('avatar', fileField.files[0]);
try {
const response = await fetch('https://example.com/profile/avatar', {
method: 'PUT',
body: formData
});
const result = await response.json();
console.log('Успех:', JSON.stringify(result));
} catch (error) {
console.error('Ошибка:', error);
}
Загрузка нескольких файлов на сервер
На сервер можно загрузить несколько файлов, используя комбинацию HTML-элемента <input type="file" multiple />, FormData() и fetch().
const formData = new FormData();
const photos = document.querySelector('input[type="file"][multiple]');
formData.append('title', 'Мой отпуск в Вегасе');
for (let i = 0; i < photos.files.length; i++) {
formData.append('photos', photos.files[i]);
}
try {
const response = await fetch('https://example.com/posts', {
method: 'POST',
body: formData
});
const result = await response.json();
console.log('Успех:', JSON.stringify(result));
} catch (error) {
console.error('Ошибка:', error);
}
Обработка текстового файла построчно
Фрагменты данных, получаемые из ответа, не разбиваются на строки автоматически (по крайней мере с достаточной точностью) и представляют собой не строки, а объекты Uint8Array. Если вы хотите загрузить текстовый файл и обрабатывать его по мере загрузки построчно, то на вас самих ложится груз ответственности за обработку всех упомянутых моментов. Как пример, далее представлен один из способов подобной обработки с помощью создания построчного итератора (для простоты приняты следующие допущения: текст приходит в кодировке UTF-8 и ошибки получения не обрабатываются).
async function* makeTextFileLineIterator(fileURL) {
const utf8Decoder = new TextDecoder("utf-8");
let response = await fetch(fileURL);
let reader = response.body.getReader();
let {value: chunk, done: readerDone} = await reader.read();
chunk = chunk ? utf8Decoder.decode(chunk) : "";
let re = /n|r|rn/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) {
//последняя строка не имеет символа перевода строки в конце
yield chunk.substr(startIndex);
}
}
for await (let line of makeTextFileLineIterator(urlOfFile)) {
processLine(line);
}
Проверка успешности запроса
В методе fetch() (en-US) promise будет отклонён (reject) с TypeError, когда случится ошибка сети или не будет сконфигурирован CORS на стороне запрашиваемого сервера, хотя обычно это означает проблемы доступа или аналогичные — для примера, 404 не является сетевой ошибкой. Для достоверной проверки успешности fetch() будет включать проверку того, что promise успешен (resolved), затем проверку того, что значение свойства Response.ok (en-US) является true. Код будет выглядеть примерно так:
try {
const response = await fetch('flowers.jpg');
if (!response.ok) {
throw new Error('Ответ сети был не ok.');
}
const myBlob = await response.blob();
const objectURL = URL.createObjectURL(myBlob);
myImage.src = objectURL;
} catch (error) {
console.log('Возникла проблема с вашим fetch запросом: ', error.message);
}
Составление своего объекта запроса
Вместо передачи пути ресурса, который вы хотите запросить вызовом fetch(), вы можете создать объект запроса, используя конструктор Request() (en-US), и передать его в fetch() аргументом:
const myHeaders = new Headers();
const myInit = {
method: 'GET',
headers: myHeaders,
mode: 'cors',
cache: 'default'
};
const myRequest = new Request('flowers.jpg', myInit);
const response = await fetch(myRequest);
const myBlob = await response.blob();
const objectURL = URL.createObjectURL(myBlob);
myImage.src = objectURL;
Конструктор Request() принимает точно такие же параметры, как и метод fetch(). Вы даже можете передать существующий объект запроса для создания его копии:
const anotherRequest = new Request(myRequest, myInit);
Довольно удобно, когда тела запроса и ответа используются единожды (прим.пер.: «are one use only»). Создание копии как показано позволяет вам использовать запрос/ответ повторно, при изменении опций init, при желании. Копия должна быть сделана до прочтения тела, а чтение тела в копии также пометит его прочитанным в исходном запросе.
Примечание: Также есть метод clone() (en-US), создающий копии. Оба метода создания копии прекратят работу с ошибкой если тело оригинального запроса или ответа уже было прочитано, но чтение тела клонированного ответа или запроса не приведёт к маркировке оригинального.
Заголовки
Интерфейс Headers (en-US) позволяет вам создать ваш собственный объект заголовков через конструктор Headers() (en-US). Объект заголовков — простая мультикарта имён-значений:
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'
});
Содержимое может быть запрошено и извлечено:
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")); // [ ]
Некоторые из этих операций могут быть использованы только в ServiceWorkers (en-US), но они предоставляют более удобный API для манипуляции заголовками.
Все методы Headers выбрасывают TypeError, если имя используемого заголовка не является валидным именем HTTP Header. Операции мутации выбросят TypeError если есть защита от мутации (смотрите ниже) (прим.пер.: «if there is an immutable guard»). В противном случае они прерываются молча. Например:
const myResponse = Response.error();
try {
myResponse.headers.set('Origin', 'http://mybank.com');
} catch (e) {
console.log('Не могу притвориться банком!');
}
Хорошим вариантом использования заголовков является проверка корректности типа контента перед его обработкой. Например:
try {
const response = await fetch(myRequest);
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
throw new TypeError("Ой, мы не получили JSON!");
}
const json = await response.json();
/_ Дальнейшая обработка JSON _/
} catch (error) {
console.log(error);
}
Защита
С тех пор как заголовки могут передаваться в запросе, приниматься в ответе и имеют различные ограничения в отношении того, какая информация может и должна быть изменена, заголовки имеют свойство guard. Это не распространяется на Web, но влияет на то, какие операции мутации доступны для объекта заголовков.
Возможные значения:
none: по умолчанию.request: защита объекта заголовков, полученного по запросу (Request.headers (en-US)).request-no-cors: защита объекта заголовков, полученного по запросу созданного с Request.mode no-cors.response: защита Headers полученных от ответа (Response.headers (en-US)).immutable: в основном, используется в ServiceWorkers; делает объект заголовков read-only.
Примечание: вы не можете добавить или установить request защищаемые Headers’ заголовок Content-Length. Аналогично, вставка Set-Cookie в заголовок ответа недопустимо: ServiceWorkers не допускают установки cookies через синтезированные ответы.
Объекты ответа
Как вы видели выше, экземпляр Response будет возвращён когда fetch() промис будет исполнен.
Свойства объекта-ответа которые чаще всего используются:
Response.status (en-US) — Целочисленное (по умолчанию 200) содержит код статуса ответа.Response.statusText (en-US) — Строка (по умолчанию»OK»), которая соответствует HTTP коду статуса.Response.ok (en-US) — как сказано ранее, это короткое свойство для упрощения проверки на то что статус ответа находится где-то между 200-299 включительно. Это свойство типа Boolean (en-US).
Они так же могут быть созданы с помощью JavaScript, но реальная польза от этого есть только при использовании сервис-воркеров (en-US), когда вы предоставляете собственный ответ на запрос с помощью метода respondWith() (en-US):
const myBody = new Blob();
addEventListener('fetch', function(event) {
// ServiceWorker перехватывает fetch
event.respondWith(
new Response(myBody, {
headers: { 'Content-Type': 'text/plain' }
})
);
});
Конструктор Response() принимает два необязательных аргумента — тело для ответа и объект init (аналогичный тому, который принимает Request() (en-US))
Примечание: Метод error() (en-US) просто возвращает ответ об ошибке. Аналогично, redirect() (en-US) возвращает ответ, приводящий к перенаправлению на указанный URL. Они также относятся только к Service Workers.
Тело
Запрос и ответ могут содержать данные тела. Тело является экземпляром любого из следующих типов:
ArrayBufferArrayBufferView (en-US) (Uint8Array и подобные)Blob/FilestringURLSearchParamsFormData
Body примесь определяет следующие методы для извлечения тела (реализованы как для Request так и для Response). Все они возвращают promise, который в конечном итоге исполняется и выводит содержимое.
arrayBuffer() (en-US)blob() (en-US)json() (en-US)text() (en-US)formData() (en-US)
Это делает использование нетекстовых данных более лёгким, чем при XMR.
В запросе можно установить параметры для отправки тела запроса:
const form = new FormData(document.getElementById('login-form'));
fetch('/login', {
method: 'POST',
body: form
});
Параметры request и response (and by extension the fetch() function), по возможности возвращают корректные типы данных. Параметр request также автоматически установит Content-Type в заголовок, если он не был установлен из словаря.
Функция обнаружения
Поддержка Fetch API может быть обнаружена путём проверки наличия Headers (en-US), Request, Response или fetch() (en-US) в области видимости Window или Worker. Для примера:
if (self.fetch) {
// запустить мой fetch запрос здесь
} else {
// Сделать что-то с XMLHttpRequest?
}
Полифил
BCD tables only load in the browser
Для того, чтобы использовать Fetch в неподдерживаемых браузерах, существует Fetch Polyfill , который воссоздаёт функциональность для не поддерживающих браузеров.
СпецификацииSpecification Status CommentFetch Живой стандарт Initial definitionСовместимость браузера
Смотрите такжеServiceWorker APIHTTP access control (CORS)HTTPFetch polyfillFetch examples on Github`
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 ifstatusis 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
I really liked @tjvantoll article Handling Failed HTTP Responses With fetch(). The one thing I found annoying with it, though, is that response.statusText always returns the generic error message associated with the error code. Most APIs, however, will generally return some kind of useful, more human friendly message in the body.
Here’s a modification that will capture this message. The key is that rather than throwing an error, you just throw the response and then process it in the catch block to extract the message in the body:
fetch("/api/foo")
.then( response => {
if (!response.ok) { throw response }
return response.json() //we only get here if there is no error
})
.then( json => {
this.props.dispatch(doSomethingWithResult(json))
})
.catch( err => {
err.text().then( errorMessage => {
this.props.dispatch(displayTheError(errorMessage))
})
})
Frankly, I’m horrified that JavaScript let’s you throw some random value, rather than an error, but hey, when in Rome…
Fetch API — это инструмент для выполнения сетевых запросов в веб-приложениях. При базовом использовании fetch() — довольно простой метод, но у него есть много нюансов. Например, мы не можем прервать fetch-запрос.
В этой статье рассмотрим сценарии использования fetch() вместе с другой возможностью языка — синтаксисом async/await. Разберемся, как получать данные, обрабатывать ошибки и отменять запросы.

Fetch API позволяет делать HTTP-запросы (GET, POST и т. д.) и обмениваться данными с сервером. Это более удобный аналог XHR.
Чтобы выполнить запрос, просто вызовите функцию fetch():
const response = await fetch(resource[, options]);
- Первый параметр
resource— это URL-адрес запроса и объект Request. - Второй (необязательный) параметр
options— это конфигурация запроса. Можно настроитьmethod,header,body,credentialsи другие опции.
Функция fetch выполняет запрос и возвращает промис, который будет ждать, когда запрос завершится. После этого промис выполняется (resolve) с объектом Response (ответ сервера). Если во время запроса произошла какая-то ошибка, промис переходит в состояние rejected.
Синтаксис async/await прекрасно сочетается с fetch() и помогает упростить работу с промисами. Давайте для примера сделаем запрос списка фильмов:
async function fetchMovies() {
const response = await fetch('/movies');
// ждем выполнения запроса
console.log(response);
}
Функция fetchMovies асинхронная, используем для ее создания ключевое слово async. Внутри она использует await, чтобы дождаться выполнения асинхронной операции fetch.
Внутри функции выполняется запрос на урл /movies. Когда он успешно завершается, мы получаем объект response с ответом сервера. Дальше в статье мы разберемся, как извлечь данные из этого объекта.
Получение JSON
Из объекта response, который возвращается из await fetch() можно извлечь данные в нескольких разных форматах. Чаще всего используется JSON:
async function fetchMoviesJSON() {
const response = await fetch('/movies');
const movies = await response.json();
return movies;
}
fetchMoviesJSON().then(movies => {
movies; // полученный список фильмов
});
Итак, чтобы извлечь полученные данные в виде JSON, нужно использовать метод response.json(). Этот метод возвращает промис, так что придется снова воспользоваться синтаксисом await, чтобы дождаться его выполнения: await response.json().
Кроме того, у объекта Response есть еще несколько полезных методов (все методы возвращают промисы):
response.json()возвращает промис, который резолвится в JSON-объект;response.text()возвращает промис, который резолвится в обычный текст;response.formData()возвращает промис, который резолвится в объект FormData;response.blob()возвращает промис, который резолвится в Blob (файлоподобный объект с необработанными данными);response.arrayBuffer()()возвращает промис, который резолвится в ArrayBuffer (необработанные двоичные данные).
Обработка ошибок
Для разработчиков, которые только начинают работать с fetch, может быть непривычным то, что этот метод не выбрасывает исключение, если сервер возвращает «плохой» HTTP-статус (клиентские 400-499 или серверные 500-599 ошибки).
Попробуем для примера обратиться к несуществующей странице /oops. Этот запрос, как и ожидается, завершается со статусом 404.
async function fetchMovies404() {
const response = await fetch('/oops');
response.ok; // => false
response.status; // => 404
const text = await response.text();
return text;
}
fetchMovies404().then(text => {
text; // => 'Page not found'
});
Из объекта response мы можем узнать, что запрос не удался, однако метод fetch() не выбрасывает ошибку, а считает этот запрос завершенным.
Промис, возвращаемый функцией fetch, отклоняется только в том случае, если запрос не может быть выполнен или ответ не может быть получен, например, из-за проблем с сетью (нет подключения, хост не найден, сервер не отвечает).
Но к счастью, у нас есть поле response.ok, с помощью которого мы можем отловить плохие статусы. Оно принимает значение true только если статус ответа 200-299.
Если вы хотите получать ошибку для всех неудачных запросов, просто выбрасывайте ее вручную:
async function fetchMoviesBadStatus() {
const response = await fetch('/oops');
if (!response.ok) {
const message = `An error has occured: ${response.status}`;
throw new Error(message);
}
const movies = await response.json();
return movies;
}
fetchMoviesBadStatus().catch(error => {
error.message; // 'An error has occurred: 404'
});
Отмена fetch-запроса
К сожалению, Fetch API не предоставляет нам никакой возможности отменить запущенный запрос. Но это можно сделать с помощью AbortController.
Чтобы объединить эти инструменты, нужно сделать 3 действия:
// Шаг 1. Создать экземпляр AbortController до начала запроса
const controller = new AbortController();
// Step 2: Передать в параметры запроса fetch() controller.signal
fetch(..., { signal: controller.signal });
// Step 3: Отменить запрос методом controller.abort при необходимости
controller.abort();
Давайте для примера создадим маленькое приложение с двумя кнопками, одна из которых будет запускать fetch-запрос, а вторая прерывать его.
let controller = null;
// Обрабатываем клики по первой кнопке
fetchMoviesButton.addEventListener('click', async () => {
controller = new AbortController();
try {
const response = await fetch('/movies', {
signal: controller.signal
});
} catch (error) {
console.log('Fetch error: ', error);
}
controller = null;
});
// Обрабатываем клики по второй кнопке
cancelFetchButton.addEventListener('click', () => {
if (controller) {
controller.abort();
}
});
Демо:
Кликните по кнопке Fetch movies, чтобы запустить запрос, а затем по кнопке Cancel fetch, чтобы отменить его. При этом возникнет ошибка, которую поймает блок .catch().
Экземпляр AbortController одноразовый, его нельзя переиспользовать для нескольких запросов. Поэтому для каждого вызова fetch нужно создавать новый инстанс.
- Как прервать fetch-запрос, если он не завершился через определенное время
Параллельные fetch запросы
Чтобы выполнять fetch-запросы параллельно, можно воспользоваться методом Promise.all().
Например, запустим сразу два запроса — для получения фильмов и для получения категорий фильмов:
async function fetchMoviesAndCategories() {
const [moviesResponse, categoriesResponse] = await Promise.all([
fetch('/movies'),
fetch('/categories')
]);
const movies = await moviesResponse.json();
const categories = await categoriesResponse.json();
return [movies, categories];
}
fetchMoviesAndCategories().then(([movies, categories]) => {
movies; // список фильмов
categories; // список категорий
}).catch(error => {
// один из запросов завершился с ошибкой
});
Фрагмент кода await Promise.all([]) запускает запросы параллельно и ожидает, когда все они перейдут в состояние resolved.
Если один из запросов завершится с ошибкой, то Promise.all тоже выбросит ошибку.
Если же вы хотите, чтобы выполнились все запросы, даже если несколько из них упали, используйте метод Promise.allSettled().
Заключение
Вызов функции fetch() запускает запрос к серверу и возвращает промис. Когда запрос успешно завершается, промис переходит в состояние resolved и возвращает объект ответа (response), из которого можно извлечь данные в одном из доступных форматов (JSON, необработанный текст или Blob).
Так как fetch возвращает промис, мы можем использовать синтаксис async/await, чтобы упростить код: response = await fetch().
В статье мы разобрали, как использовать эту комбинацию для получения данных, обработки ошибок, отмены запросов и выполнения параллельных запросов.
Fetch API используется для выполнения ajax-запросов, для вызова API или выборки удаленного ресурса или получения HTML-файла с сервера.

Базовый синтаксис Fetch API
Допустим, нужно получить список сообщений из API https://jsonplaceholder.typicode.com/posts. Во-первых, следует передать этот URL в метод fetch() в качестве аргумента.
fetch('https://jsonplaceholder.typicode.com/posts');
Метод fetch() возвращает Promise. Поэтому ответы API нужно обрабатывать с использованием then() и catch().
fetch('https://jsonplaceholder.typicode.com/posts')
.then(function (response) {
// API вернул результат
console.log('success!', response);
}).catch(function (err) {
// Что-то пошло не так
console.warn('Ошибка получения.');
console.log(err);
});
Посмотрите пример.
Посмотрим на ответ в консоли: сразу можно обратить внимание, в response.body нет JSON. Это — ReadableStream.
Fetch API использует потоки. Чтобы получить данные из API в виде объекта JSON, можно использовать оригинальный метод Fetch API: json().
fetch('https://jsonplaceholder.typicode.com/posts')
.then(function (response) {
// API вернул результат
return response.json();
}).then(function (data) {
// Это уже JSON
console.log(data);
}).catch(function (err) {
// Что-то пошло не так
console.warn('Ошибка получения.');
console.log(err);
});
Посмотрите, теперь тут нормальный JSON.
Обработка ошибок для Fetch API
Fetch API обрабатывает ошибки с помощью метода catch(), потому что он возвращает Promise.
Однако, Promise отклоняется и вызывает метод catch() только в случае сбоя. Если вернулся ответ от сервера, даже в случае с ошибками 404 или 500, будут работать методы then().
Чтобы пофиксить такое поведение, можно использовать свойство ok в response, который возвращает Promise.
Если у свойства response.ok значение true, вернём response.json(). Если нет, то вернём отклоненный объект Promise и передадим в нём ответ, чтобы вызвать метод catch().
fetch('https://jsonplaceholder.typicode.com/postsQQQQ')
.then(function (response) {
// API вернул результат
if (response.ok) {
return response.json();
} else {
console.log('Это не 200OK')
return Promise.reject(response);
}
}).then(function (data) {
// Это уже JSON
console.log(data);
}).catch(function (err) {
// Что-то пошло не так
console.warn('Ошибка получения.');
console.log(err);
});
Так лучше обрабатывать ошибки в методе Fetch.
В этом примере умышленно искажён URL, он вернёт 404 ошибку, чтобы продемонстрировать отклонение Promise вручную для принудительного вызова метода catch().
То же самое можно делать с помощью XHR
Обработчик onreadystatechange в XHR тоже выполняется независимо от того, был ответ успешным или нет. Здесь тоже нужно проверять, находится ли ответ между 200 и 300, прежде чем работать с ним.
xhr.onreadystatechange = function () {
// Вернём, когда работа метода завершится
if (xhr.readyState !== 4) return;
// обработаем ответ
if (xhr.status >= 200 && xhr.status < 300) {
// Ответ нормальный
console.log('success', JSON.parse(xhr.responseText));
} else {
// Ответ плохой
console.log('error', xhr);
}
};
У Fetch API код получился намного чище, сравните.
Создание других типов запросов
По умолчанию Fetch API выполняет GET-запросы. Допустим, нужно сделать POST-запрос для публикации новой статьи через API. Методу fetch() надо в этом случае передавать второй аргумент, который можно использовать для передачи объекта с параметрами.
fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST'
}).then(function (response) {
// API вернул результат
if (response.ok) {
return response.json();
} else {
console.log('Это не 200OK')
return Promise.reject(response);
}
}).then(function (data) {
// Это уже JSON
console.log(data);
}).catch(function (err) {
// Что-то пошло не так
console.warn('Ошибка получения.');
console.log(err);
});
Для определенных типов запросов может понадобиться передавать данные вместе с запросом. Это можно сделать с помощью свойства body в объекте параметров.
fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
body: 'title=' + encodeURIComponent('Заголовок') +
'&body=' + encodeURIComponent('Текст статьи')
}).then(function (response) {
// API вернул результат
if (response.ok) {
return response.json();
} else {
console.log('Это не 200OK')
return Promise.reject(response);
}
}).then(function (data) {
// Это уже JSON
console.log(data);
}).catch(function (err) {
// Что-то пошло не так
console.warn('Ошибка получения.');
console.log(err);
});
Установка заголовков с помощью Fetch API
Еще одна распространенная вещь, которую может потребоваться сделать, установить для запроса заголовки и другие свойства. Это также можно сделать с помощью объекта параметров.
fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
body: 'title=' + encodeURIComponent('Заголовок') +
'&body=' + encodeURIComponent('Текст статьи'),
headers: {
'Content-Type': 'application/json'
},
referrer: 'no-referrer'
}).then(function (response) {
// API вернул результат
if (response.ok) {
return response.json();
} else {
console.log('Это не 200OK')
return Promise.reject(response);
}
}).then(function (data) {
// Это уже JSON
console.log(data);
}).catch(function (err) {
// Что-то пошло не так
console.warn('Ошибка получения.');
console.log(err);
});
Демка
Здесь можно поэкспериментировать с кросс-доменными запросами (CORS).
See this code cors fetch on x.xhtml.ru.
How to use the Fetch API with vanilla JS