Запросы и ответы с fetch в javascript
Работаем с запросами и ответами с сервера с помощью fetch api
fetch
В продолжении темы про Promises
, теперь рассмотрим fetch
.
Fetch
- это современный способ послать и получить запрос с сервера.
fetch
принимает два параметра url
запроса и настройки запроса и возвращает Promise
со специальным объектом Response
.
Ранее для этой цели использовался объет XMLHttpRequest
.
Если сходу попробовать отправить запрос на несуществующий url
, то ответ будет вполне ожидаемым.
В ответе будет Promise
и ошибка, что адреса нет.
1
2
fetch('123123213'); // Promise { <state>: "pending" } 404 not found
fetch('https://jsonplaceholder.typicode.com/fake'); // Promise { <state>: "pending" } 404 not found
Теперь отправим корректный запрос, на любой сервис или сервер, который может дать ответ
Я буду использовать сервисы:
Если ничего не указать запрос по умолчанию будет методом GET
1
2
3
4
let request = fetch('https://jsonplaceholder.typicode.com/posts');
console.log(request); //Promise { <state>: "fulfilled" }
// Объект Response
request.then(response => {console.log(response)}); //Response { type: "cors", url: "https://jsonplaceholder.typicode.com/posts", redirected: false, status: 200, ok: true, statusText: "", headers: Headers(4), body: ReadableStream, bodyUsed: false }
Отправим запрос и сразу же его обработаем.
В качестве ответа fetch возвращает объект Response
1
2
3
let request = fetch('https://fakerapi.it/api/v2/texts?_quantity=100&_characters=500&_locale=RU_ru');
request.then(response => {console.log(response.json())});
// Response { type: "cors", url: "https://fakerapi.it/api/v2/texts?_quantity=100&_characters=500&_locale=ru_RU", redirected: false, status: 200, ok: true, statusText: "", headers: Headers(4), body: ReadableStream, bodyUsed: false }
Получаем как ни странно, тоже Promise
с value
с объектом Response
, после его обработки получаем объект Response
.
Но просто так вытащить оттуда данные не получиться, так как ответ это тоже Promise
.
Обрабатываем второй раз Promise
. И вот тогда наконец, достучимся до результата.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let request = fetch('https://fakerapi.it/api/v2/texts?_quantity=100&_characters=500&_locale=ru_RU');
request
.then(response => {
// Объект Response предоставляет метод json
// Здесь response.json() promise;
return response.json()
})
// Дообрабатываем его
.then(json => {
// Наконец получаем массив результата
// Здесь мы можем работать с DOM, куками, обрабатывать результат
console.log(json.data); // []
})
Эксперименты с несколькими запросами
Теперь когда мы научились отправлять запросы и получать ответ, поэкспериментируем с несколькими запросами.
Сделаем 5 функций, которые будут отправлять запрос, и обработаем его.
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
function request(){
console.log('request')
return fetch('https://fakerapi.it/api/v2/texts?_quantity=100&_characters=500&_locale=ru_RU');
}
function request2(){
console.log('request2')
return fetch('https://fakerapi.it/api/v2/texts?_quantity=100&_characters=500&_locale=ru_RU');
}
function request3(){
console.log('request3')
return fetch('https://fakerapi.it/api/v2/texts?_quantity=100&_characters=500&_locale=ru_RU');
}
function request4(){
console.log('request4')
return fetch('https://fakerapi.it/api/v2/texts?_quantity=100&_characters=500&_locale=ru_RU');
}
function request5(){
console.log('request5')
return fetch('https://fakerapi.it/api/v2/texts?_quantity=100&_characters=500&_locale=ru_RU');
}
request()
.then(response => { return response.json()})
.then(json => { console.log('request');console.log(json.data);})
request2()
.then(response => { return response.json()})
.then(json => { console.log('request2');console.log(json.data);})
request3()
.then(response => { return response.json()})
.then(json => { console.log('request3');console.log(json.data);})
request4()
.then(response => { return response.json()})
.then(json => { console.log('request4');console.log(json.data);})
request5()
.then(response => { return response.json()})
.then(json => { console.log('request5');console.log(json.data);})
Казалось бы, запросы идут друг за другом, что и выводится в консоль в начале
1
2
3
4
5
request
request2
request3
request4
request5
Но при обработке, console.log
показывает каждый раз разный результат.
Я запустил несколько раз и результаты получились такие:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
request2
request4
request3
request5
request
request
request5
request4
request2
request3
request
request3
request2
request5
request4
Так получается, потому что запросы на сервер поступают ассинхронно все сразу, и им для выполнения нужно разное время. Как только запрос выполнился ответ, пришел.
Можно загнать все в одну функцию:
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
40
41
42
43
function requests() {
console.log('request');
fetch('https://fakerapi.it/api/v2/texts?_quantity=100&_characters=500&_locale=ru_RU')
.then(response => {
return response.json()
})
.then(json => {
console.log('request')
});
console.log('request2');
fetch('https://fakerapi.it/api/v2/texts?_quantity=100&_characters=500&_locale=ru_RU')
.then(response => {
return response.json()
})
.then(json => {
console.log('request2')
});
console.log('request3');
fetch('https://fakerapi.it/api/v2/texts?_quantity=100&_characters=500&_locale=ru_RU')
.then(response => {
return response.json()
})
.then(json => {
console.log('request3')
});
console.log('request4');
fetch('https://fakerapi.it/api/v2/texts?_quantity=100&_characters=500&_locale=ru_RU')
.then(response => {
return response.json()
})
.then(json => {
console.log('request4')
});
console.log('request5');
fetch('https://fakerapi.it/api/v2/texts?_quantity=100&_characters=500&_locale=ru_RU')
.then(response => {
return response.json()
})
.then(json => {
console.log('request5')
});
}
requests();
Но результат от этого не поменяется.
1
2
3
4
5
6
7
8
9
10
request
request2
request3
request4
request5
request
request3
request5
request2
request4
Как же нам обработать эти запросы последовательно.
Воспользуемся синтаксисом async/await
, в отдельной функции
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
async function requests(){
console.log('request');
const response1 = await fetch('https://fakerapi.it/api/v2/texts?_quantity=100&_characters=500&_locale=ru_RU');
const data1 = await response1.json();
console.log('request');
console.log('request2');
const response2 = await fetch('https://fakerapi.it/api/v2/texts?_quantity=100&_characters=500&_locale=ru_RU');
const data2 = await response2.json();
console.log('request2');
console.log('request3');
const response3 = await fetch('https://fakerapi.it/api/v2/texts?_quantity=100&_characters=500&_locale=ru_RU');
const data3 = await response3.json();
console.log('request3');
console.log('request4');
const response4 = await fetch('https://fakerapi.it/api/v2/texts?_quantity=100&_characters=500&_locale=ru_RU');
const data4 = await response4.json();
console.log('request4');
console.log('request5');
const response5 = await fetch('https://fakerapi.it/api/v2/texts?_quantity=100&_characters=500&_locale=ru_RU');
const data5 = await response5.json();
console.log('request5');
}
requests();
В результате в ответе получаем последовательное выполнение каждого запроса, так как нам и нужно.
1
2
3
4
5
6
7
8
9
10
request
request
request2
request2
request3
request3
request4
request4
request5
request5
Еще неплохой способ создания нескольких запросов сразу использование Promise.all
.
Результат будет возвращен в виде массива.
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
async function requests2()
{
// Готовим запросы
let url = 'https://fakerapi.it/api/v2/texts?_quantity=99&_characters=500&_locale=ru_RU';
let url2 = 'https://fakerapi.it/api/v2/texts?_quantity=100&_characters=500&_locale=ru_RU';
let url3 = 'https://fakerapi.it/api/v2/texts?_quantity=101&_characters=500&_locale=ru_RU';
let url4 = 'https://fakerapi.it/api/v2/texts?_quantity=102&_characters=500&_locale=ru_RU';
let url5 = 'https://fakerapi.it/api/v2/texts?_quantity=103&_characters=500&_locale=ru_RU';
const responses = await Promise.all([fetch(url), fetch(url2),fetch(url3),fetch(url4),fetch(url5)]);
// Выводим результат
const data1 = await responses[0].json();
console.log(data1.total);
const data2 = await responses[1].json();
console.log(data2.total);
const data3 = await responses[2].json();
console.log(data3.total);
const data4 = await responses[3].json();
console.log(data4.total);
const data5 = await responses[4].json();
console.log(data5.total);
}
requests2();
Здесь в ответах, чтобы их отличать, я поменял GET
параметр, где его буду получать в ответе.
При этом порядок передачи Promise
тут важен, в этом же порядке будет ответ.
Про дополнительные методы Promise
, будет отдельная статья.
1
2
3
4
5
99
100
101
102
103
Можно делать зависимые запросы, здесь думаю, проблем не должно быть.
Важно на что нужно обращать внимание!
Результат из функции которая возвращает Promise нельзя получить по определению, такова природа асинхронных функций. Его нужно обязательно обработать с помощью then или await
Обработка ошибок запроса
Все хорошо, но результат не всегда положительный, бывают ошибки.
Следующий вопрос, как обротать ошибки, например вернуть результат если что-то не найдено.
catch
для это не пойдет, так как Promise
по умолчанию разрешен, то есть мы попадаем в then
. Там и нужно обрабатывать ошибки.
Но не все так очевидно.
Делаем запрос на несуществующий адрес
1
2
3
4
5
6
fetch('https://fakerapi.it/api/v2/ertretretretrtret')
.then(response => {
console.log(response);
})
// Попадаем сразу в then, получаем ошибку 404 что адреса нет и объект response со статусом 404
Но как то непонятно почему мы попали в then
, попробуем разобраться.
Здесь важно понимать два понятия:
- Запрос успешно достиг сервера
- Ответ от сервера успешно пришел
Запрос успешно достиг сервера
Если запрос был отправлен и успешно достиг сервера, с точки зрения fetch
задача выполнена.
1
2
3
4
5
6
7
8
9
10
fetch('https://fakerapi.it/api/v2/ertretretre')
.then(response => {
// Запрос успешно достиг сервера, но в ответе 404
// И тут у нас такое сообщение Response.json: Body has already been consumed.
return response.json()
})
.catch((error) => {
// До сюда мы так и не дошли, проверки нужно делать в then
console.log(error.message)
})
Но если адрес будет не валидный в catch
мы все-такие попадаем
1
2
3
4
5
6
7
8
9
fetch('апрпарппара')
.then(response => {
console.log(response); // Response есть всегда
return response.json()
})
.catch((error) => {
// Не можем распарсить json
console.log(error.message) // JSON.parse: unexpected character at line 1 column 1 of the JSON data
})
Ответ от сервера успешно пришел
Сервер может вернуть ответ со статусами 200,404,500,403
и другими, при этом в catch
это все не попадает, так как запрос успешно достиг сервера.
Вот почему нужно проверять ответ в then
.
Выполним успешный и неуспешный запрос к серверу
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
fetch('https://dummyjson.com/http/200')
.then(response => {
console.log(response.ok);
console.log(response.status);
console.log(response.statusText);
/*
true
200
OK
*/
return response.json()
})
// До сюда мы по прежнему не дойдем
.catch((error) => {
console.log(error.message)
})
fetch('https://dummyjson.com/http/500')
.then(response => {
console.log(response.ok);
console.log(response.status);
console.log(response.statusText);
return response.json()
/*
false
500
Internal Server Error
*/
})
// До сюда мы по прежнему не дойдем
.catch((error) => {
console.log(error.message)
})
Все дело в том, что в catch
попадут только исключения, а статус 404
, и 500
это просто статус.
Как тут правильно поступить, нужно выбросить исключение в then
. Примерно так:
1
2
3
4
5
6
7
8
9
10
11
fetch('https://dummyjson.com/http/404')
.then(response => {
if(!response.ok) {
throw new Error(`Произошла ошибка: ${response.statusText} - ${response.status}`)
}
return response.json()
})
.catch((error) => {
// Произошла ошибка: Not Found - 404
console.log(error.message) // Перехват произойдет здесь
})
Вот теперь лучше, мы обрабатываем ошибки сервера.
catch обрабатывает только исключения, ответы сервера такие как 404,500 нужно обработать вручную и выбросить исключение в then
Значение response.ok == true только для статусов ответа сервера в диапазоне 200-299, для остальных оно равно false
Полный пример, пока у нас выглядит так
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fetch('https://fakerapi.it/api/v2/texts?_quantity=100&_characters=500&_locale=ru_RU')
.then(response => {
if(!response.ok) {
throw new Error(`Произошла ошибка: ${response.statusText} - ${response.status}`)
}
return response.json()
})
.then(json => {
// Какая-то работа с ответом
console.log(json.data)
})
.catch((error) => {
// Произошла ошибка: Not Found - 404
console.log(error.message)
})
Усложним наши примеры. Например, вернем последние коммиты из репозитория php
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fetch('https://api.github.com/repos/php/php-src/commits')
.then(response => {
if(!response.ok) {
throw new Error(`Произошла ошибка: ${response.statusText} - ${response.status}`)
}
return response.json()
})
.then(json => {
// Какая-то работа с ответом
console.log(json) // Ответ
})
.catch((error) => {
// Произошла ошибка: Not Found - 404
console.log(error.message)
})
Подсократим запись на синтаксис async/await
1
2
3
4
5
6
7
8
9
async function test(){
let response = await fetch('https://api.github.com/repos/php/php-src/commits');
if(!response.ok) {
throw new Error(`Произошла ошибка: ${response.statusText} - ${response.status}`)
}
console.log(await response.json());
}
test();
Далее рассмотрим нюансы использования.
Строка в качестве ответа
Чаще всего ответ нужен в json
, но его можно вернуть как текст в виде строки, для этого воспользуемся response.text()
.
1
2
3
4
5
6
7
8
9
async function test(){
let response = await fetch('https://api.github.com/repos/php/php-src/commits');
if(!response.ok) {
throw new Error(`Произошла ошибка: ${response.statusText} - ${response.status}`)
}
console.log(await response.text());
}
test();
Можно выбрать только один метод чтения ответа.
Файл в качестве ответа
Так же с помощью fetch
можно сохранить фаил с сервера. Все зависит от задачи, но представим, что нужно сохранить файл на компьютер.
Чтобы эмулировать сохранение файла, напишем вспомогательную функцию, которая будет сохранять файл на компьютер, некая эмуляция ссылки и клика по ней.
1
2
3
4
5
6
7
8
function saveFile(url, filename) {
const a = document.createElement("a");
a.href = url;
a.download = filename || "file-name";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
Для работы с бинарными файлами есть метод response.blob()
, он нам и пригодится.
1
2
3
4
5
6
7
8
9
10
11
fetch('https://dummyjson.com/image/1000x1000/008080/ffffff?text=Lexusalex!&fontSize=55')
.then(response => response.blob())
.then(blob => {
// уникальный url для бинарного объекта
let url = URL.createObjectURL(blob);
// сохранение файла
saveFile(url, "image");
// удаление ссылки на бинарный объект
window.URL.revokeObjectURL(url);
//console.log('Fetched image blob:', blob);
})
То есть где, то на сервере есть изображение, мы его берем как бинарный фаил преобразуем в изображение и сохраняем на компьютер.
Заголовки
Ответ
Можно получить доступ к заголовкам ответа через response.headers
- это объект с заголовками ответа
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fetch('https://api.github.com/repos/php/php-src/commits')
.then(response => {
if(!response.ok) {
throw new Error(`Произошла ошибка: ${response.statusText} - ${response.status}`)
}
console.log(response.headers); // Заголовки
return response.json()
})
.then(json => {
// Какая-то работа с ответом
console.log(json)
})
.catch((error) => {
console.log(error.message)
})
Объект заголовков ответа будет выглядеть примерно так.
1
Headers(12) { "cache-control" → "public, max-age=60, s-maxage=60", "content-type" → "application/json; charset=utf-8", etag → 'W/"ad760697e9057cb61ad4e964f8726f33d7a490c4c565cc2a6ba98f3e1e522450"', "last-modified" → "Tue, 07 Jan 2025 09:20:36 GMT", link → '<https://api.github.com/repositories/1903522/commits?page=2>; rel="next", <https://api.github.com/repositories/1903522/commits?page=4666>; rel="last"', "x-github-media-type" → "github.v3; format=json", "x-github-request-id" → "26C2:3E0C3E:2909D61:29F16C9:677D3575", "x-ratelimit-limit" → "60", "x-ratelimit-remaining" → "58", "x-ratelimit-reset" → "1736262472", … }
Запрос
Так же есть возможность установить заголовки запроса, например
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fetch('https://api.github.com/repos/php/php-src/commits',{
// Здесь можно установить заголовки запроса
headers: {
'Content-Type': 'application/xml'
}
})
.then(response => {
if(!response.ok) {
throw new Error(`Произошла ошибка: ${response.statusText} - ${response.status}`)
}
return response.json()
})
.then(json => {
// Какая-то работа с ответом
console.log(json)
})
.catch((error) => {
console.log(error.message)
})
Добавление данных методом POST
До этого момента мы отправляли данные методом GET
, fetch
так же умеет отправлять данные и другими методами, посмотрим на метод POST
.
1
2
3
4
5
6
7
8
9
fetch('https://dummyjson.com/products/add', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: 'Новая запись',
})
})
.then(res => res.json())
.then(console.log);
Ответ в данном пример будет возвращен со статусом 201
- ресурс создан.
Обновить данные методом PUT или PATCH
Теперь обновим ресурс используя метод PUT
.
1
2
3
4
5
6
7
8
9
fetch('https://dummyjson.com/products/77', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: 'Новое название'
})
})
.then(res => res.json())
.then(console.log);
или PATCH
1
2
3
4
5
6
7
8
9
fetch('https://dummyjson.com/products/77', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: 'Новое название'
})
})
.then(res => res.json())
.then(console.log);
На сервере с фейковыми данными оба метода возвращают одно и тоже, это так настроено.
Удалить объект методом DELETE
Ну и попробуем удалить ресурс методом DELETE
.
1
2
3
4
5
fetch('https://dummyjson.com/products/1', {
method: 'DELETE',
})
.then(res => res.json())
.then(console.log);
Что в итоге
fetch
сейчас это мощный инструмент для отправки и получения запросов по сети, он предоставляет простой и гибкий способ работы с запросами ответами.
Мы рассмотрели основные варианты использование fetch
.
Некоторые темы мы не затронули, они будут рассмотрены позднее.
Есть вопрос или дополнение к статье, пишите https://t.me/lexus7alex
Хотите оптимизировать свой бизнес, нужен сервис, сайт или интеграция.
Бесплатно расчитаю время разработки, предложу решение вашей задачи.