Кратко
СкопированоС помощью функции fetch можно отправлять сетевые запросы на сервер — как получать, так и отправлять данные. Метод возвращает промис с объектом ответа, где находится дополнительная информация (статус ответа, заголовки) и ответ на запрос.
Как понять
СкопированоБраузер предоставляет глобальный API для работы с запросами и ответами HTTP. Раньше для подобной работы использовался XMLHttpRequest, однако fetch более гибкая и мощная альтернатива, он понятнее и проще в использовании из-за того, что использует Promise.
Как пишется
СкопированоФункция fetch принимает два параметра:
url— адрес, по которому нужно сделать запрос;options(необязательный) — объект конфигурации, в котором можно настроить метод запроса, тело запроса, заголовки и многое другое.
По умолчанию вызов fetch делает GET-запрос по указанному адресу. Базовый вызов для получения данных можно записать таким образом:
fetch('http://jsonplaceholder.typicode.com/posts')
fetch('http://jsonplaceholder.typicode.com/posts')
Результатом вызова fetch будет Promise, в котором будет содержаться специальный объект ответа Response. У этого объекта есть два важных для нас поля:
ok— принимает состояниеtrueилиfalseи сообщает об успешности запроса;json— метод, вызов которого, возвращает результат запроса в виде json.
В следующем примере используем .then - обработчик результата, полученного от асинхронной операции. Обработчик дождётся ответа от сервера, принимает ответ, и, в данном случае, неявно возвратит ответ, обработанный методом .json();
fetch('http://jsonplaceholder.typicode.com/posts') // функция then вернет другой промис (их можно чейнить). Когда отрезолвится промис (r.json()), который вернула функция then, будет вызван следующий колбек в цепочке .then((response) => response.json()) // Получим ответ в виде массива из объектов [{...}, {...}, {...}, ...]
fetch('http://jsonplaceholder.typicode.com/posts')
// функция then вернет другой промис (их можно чейнить). Когда отрезолвится промис (r.json()), который вернула функция then, будет вызван следующий колбек в цепочке
.then((response) => response.json())
// Получим ответ в виде массива из объектов [{...}, {...}, {...}, ...]
С помощью второго аргумента options можно передать настройки запроса. Например, можно изменить метод и добавить тело запроса, если мы хотим не получать, а отправлять данные. Так же в запрос можно добавить заголовки в виде объекта или специального класса Headers.
const newPost = { title: 'foo', body: 'bar', userId: 1,}fetch('https://jsonplaceholder.typicode.com/posts', { method: 'POST', // Здесь так же могут быть GET, PUT, DELETE body: JSON.stringify(newPost), // Тело запроса в JSON-формате headers: { // Добавляем необходимые заголовки 'Content-type': 'application/json; charset=UTF-8', },}) .then((response) => response.json()) .then((data) => { console.log(data) // {title: "foo", body: "bar", userId: 1, id: 101} })
const newPost = {
title: 'foo',
body: 'bar',
userId: 1,
}
fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST', // Здесь так же могут быть GET, PUT, DELETE
body: JSON.stringify(newPost), // Тело запроса в JSON-формате
headers: {
// Добавляем необходимые заголовки
'Content-type': 'application/json; charset=UTF-8',
},
})
.then((response) => response.json())
.then((data) => {
console.log(data)
// {title: "foo", body: "bar", userId: 1, id: 101}
})
Cookies
По умолчанию fetch запросы не включают в себя cookies и потому авторизованные запросы на сервере могут не пройти. Для этого необходимо добавить в настройку поле credentials:
fetch('https://somesite.com/admin', { method: 'GET', // или 'same-origin' если можно делать такие запросы только в пределах этого домена credentials: 'include',})
fetch('https://somesite.com/admin', {
method: 'GET',
// или 'same-origin' если можно делать такие запросы только в пределах этого домена
credentials: 'include',
})
Обработка ошибок
СкопированоЛюбой ответ на запрос через fetch (например HTTP-код 400, 404 или 500) переводит Promise в состояние fulfilled. Промис перейдёт в состояние rejected только если запрос не случился из-за сбоя сети или что-то помешало выполнению fetch.
// Запрос вернет ошибку 404 Not Foundfetch('https://jsonplaceholder.typicode.com/there-is-no-such-route').catch( () => { console.log('Error occurred!') }) // Никогда не выполнится
// Запрос вернет ошибку 404 Not Found
fetch('https://jsonplaceholder.typicode.com/there-is-no-such-route').catch(
() => {
console.log('Error occurred!')
}
) // Никогда не выполнится
Чтобы обработать ошибку запроса, необходимо обращать внимание на поле ok в объекте ответа Response. В случае ошибки запроса оно будет равно false.
fetch('https://jsonplaceholder.typicode.com/there-is-no-such-route') .then((response) => { // Проверяем успешность запроса и выкидываем ошибку if (!response.ok) { throw new Error('Error occurred!') } return response.json() }) // Теперь попадём сюда, т.к выбросили ошибку .catch((err) => { console.log(err) }) // Error: Error occurred!
fetch('https://jsonplaceholder.typicode.com/there-is-no-such-route')
.then((response) => {
// Проверяем успешность запроса и выкидываем ошибку
if (!response.ok) {
throw new Error('Error occurred!')
}
return response.json()
})
// Теперь попадём сюда, т.к выбросили ошибку
.catch((err) => {
console.log(err)
}) // Error: Error occurred!
На практике
Скопированосоветует
СкопированоОтмена запроса
СкопированоИногда может возникнуть необходимость прервать запрос, например когда авторизация пользователя просрочена или пользователь самостоятельно хочет отменить запрос (отменил скачивание файла).
const controller = new AbortController()function fetchData() { return fetch('http://jsonplaceholder.typicode.com/posts', { signal: controller.signal, }) .then((response) => response.json()) .catch((e) => { console.log(e) })}fetchData()// Если запрос еще не выполнился, то он будет прерван// прерванный fetch вернет Promise с ошибкойcontroller.abort()
const controller = new AbortController()
function fetchData() {
return fetch('http://jsonplaceholder.typicode.com/posts', {
signal: controller.signal,
})
.then((response) => response.json())
.catch((e) => {
console.log(e)
})
}
fetchData()
// Если запрос еще не выполнился, то он будет прерван
// прерванный fetch вернет Promise с ошибкой
controller.abort()
Запрос не выполнится, в консоли будет ошибка The user aborted a request. Если заглянуть в инструменты разработчика, то там можно увидеть отменённый статус у запроса.
Загрузка файла на сервер
СкопированоС помощью fetch можно загружать файлы на сервер, например когда пользователь хочет загрузить свой аватар в профиль. Отправку файлов можно осуществлять с помощью специального объекта Form. Покажем на примере обработчика отправки формы:
<form id="form"> <input type="file" id="avatar"> <button type="submit">Загрузить</button></form>
<form id="form">
<input type="file" id="avatar">
<button type="submit">Загрузить</button>
</form>
// Находим элемент с файломconst fileInput = document.getElementById('avatar')const form = document.getElementById('form')function handleSubmit(event) { event.preventDefault() const formData = new FormData() // Добавляем файлы из инпута к данным for (let i = 0; i < fileInput.files.length; i++) { const file = fileInput.files[i] formData.append('avatar', file, file.name) } // Отправляем файлы на сервер fetch('https://backend.com/api/upload', { method: "POST", body: formData, })}form.addEventListener('submit', handleSubmit)
// Находим элемент с файлом
const fileInput = document.getElementById('avatar')
const form = document.getElementById('form')
function handleSubmit(event) {
event.preventDefault()
const formData = new FormData()
// Добавляем файлы из инпута к данным
for (let i = 0; i < fileInput.files.length; i++) {
const file = fileInput.files[i]
formData.append('avatar', file, file.name)
}
// Отправляем файлы на сервер
fetch('https://backend.com/api/upload', {
method: "POST",
body: formData,
})
}
form.addEventListener('submit', handleSubmit)
Скачивание данных с результатом прогресса
СкопированоЧтобы получать текущий прогресс скачивания файла или любых других данных нам понадобится использовать свойство body объекта Response, который возвращается в Promise после вызова fetch. Поле body является «потоком для чтения» (Readable Stream) — это специальный объект, который даёт возможность получать информацию по частям, по мере её поступления на клиент.
Попробуем таким образом загрузить милое видео как белый котик дружит с огромным псом.
fetch('https://i.imgur.com/C5QXZ7u.mp4').then(async (response) => { let received = 0 // Получаем поток в переменную const reader = response.body.getReader() // Считываем общую длину данных const contentLength = parseInt(response.headers.get('Content-Length'), 10) while (true) { // После вызова read() возвращается объект, в котором // done — boolean-значение о том закончилась ли информация // value — массив байт, которые пришли в этот раз const { done, value } = await reader.read() if (done) { console.log('Получено 100%') break } received += Math.ceil(contentLength / value.length) console.log(`Получено ${received}%`) }})
fetch('https://i.imgur.com/C5QXZ7u.mp4').then(async (response) => {
let received = 0
// Получаем поток в переменную
const reader = response.body.getReader()
// Считываем общую длину данных
const contentLength = parseInt(response.headers.get('Content-Length'), 10)
while (true) {
// После вызова read() возвращается объект, в котором
// done — boolean-значение о том закончилась ли информация
// value — массив байт, которые пришли в этот раз
const { done, value } = await reader.read()
if (done) {
console.log('Получено 100%')
break
}
received += Math.ceil(contentLength / value.length)
console.log(`Получено ${received}%`)
}
})