Fetch
Fetch API предоставляет упрощенный и в тоже время гибкий и мощный инструмент для обращения к сетевым ресурсам по сравнению со стандартным XMLHttpRequest. Ключевым элементом этого Fetch API является функция fetch(). Она реализована в различных контекстах. В частности, в браузере она реализована интерфейсом Windows.
Функция fetch имеет следующий синтаксис:
let fetchPromise = fetch(url, options)
urlURL для отправки запросаoptionsдополнительные параметры — метод, заголовки и так далее
Без options это простой GET-запрос, скачивающий содержимое по адресу url. Функция fetch() возвращает объект Promise, который получает ответ после завершения запроса к сетевому ресурсу. promise выполняется с объектом встроенного класса Response в качестве результата, как только сервер пришлёт заголовки ответа.
Отправка запроса и чтение ответа
Если не указывать метод, то Fetch API по умолчанию делает GET-запрос:
let response = fetch(url);
index.html<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>hmarketing.ru</title>
</head>
<body>
<div></div>
<button>Нажми</button>
<script>
let div = document.querySelector("div");
let button = document.querySelector("button");
button.addEventListener("click", function () {
// запрос к файлу ajax.html
let response = fetch("/ajax.html");
response
// в первом then находится информация о запросе и в каком формате мы получим ответ
.then((response) => {
if (response.ok) {
// возвращаем тело ответа
return response.text();
}
})
// в втором then находится ответ на запрос
.then((text) => {
// выведем данные
div.innerHTML = text;
})
// в catch мы попадём когда fetch() вообще не может выполнить запрос, т.е. нет такого сайта или произошла потеря сетевого соединения
.catch((error) => {
console.log(error);
});
});
</script>
</body>
</html>
При успешном выполнении запроса, мы получим объект Response в первом then. У Response есть ряд полезных свойств для проверки состояния ответа:
bodyсодержимое ответа в виде объектаReadableStreambodyUsedхранит булевое значение, которое указывает, было ли содержимое ответа уже использовано.headersнабор заголовков ответа в виде объектаHeadersokхранит булевое значение, которое указывает, завершился ли запрос успешно (то есть если статусной код ответа находится в диапазоне 200−299)redirectedхранит булевое значение, которое указывает, является ли ответ результатом переадресацииstatusхранит статусный код ответаstatusTextхранит сообщение статуса, которое соответствует статусному кодуtypeхранит тип ответаurlхранит адрес URL. Если в процессе запроса происходит ряд переадресаций, то хранит конечный адрес URL после всех переадресаций
Для получения тела ответа, у объекта Response в первом then имеются следующие методы:
response.text()читает ответ и возвращает как обычный текст,response.json()декодирует ответ в формате JSONresponse.formData()возвращает ответ как объектFormDataresponse.blob()возвращает объект какBlob(бинарные данные с типом)response.arrayBuffer()— возвращает ответ какArrayBuffer(низкоуровневое представление бинарных данных)response.bodyэто объект ReadableStream, с помощью которого можно считывать тело запроса по частям
Отправка запроса и чтение ответа через async-await
Ключевое слово await заставит интерпретатор JavaScript ждать до тех пор, пока промис справа от await не выполнится. После чего оно вернёт его результат, и выполнение кода продолжится.
index.html<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>hmarketing.ru</title>
</head>
<body>
<div></div>
<button>Нажми</button>
<script>
let div = document.querySelector("div");
let button = document.querySelector("button");
// функция, возвращающая промис
function promis(ms) {
return new Promise((resolve) =>
setTimeout(() => {
resolve(console.log(2));
}, ms)
);
}
button.addEventListener("click", async function () {
console.log(1);
try {
// запрос к файлу ajax.html
let response = await fetch("/ajax.html");
// получаем информацию о запросе и в каком формате мы получим ответ
if (response.ok) {
// получаем тело ответа
let data = await response.text();
// ждем выполнения промиса
await await promis(1000);
// выведем данные
div.innerHTML = data;
}
} catch (error) {
console.log(error);
}
console.log(3);
});
</script>
</body>
</html>
Заголовки ответа
Заголовки ответа хранятся в похожем на Map объекте response.headers.
Это не совсем Map, но мы можем использовать такие же методы, как с Map, чтобы получить заголовок по его имени:
index.html<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>hmarketing.ru</title>
</head>
<body>
<div></div>
<button>Нажми</button>
<script>
let div = document.querySelector("div");
let button = document.querySelector("button");
button.addEventListener("click", function () {
// запрос к файлу ajax.html
let response = fetch("/ajax.html");
response
// в первом then находится информация о запросе и в каком формате мы получим ответ
.then((response) => {
if (response.ok) {
// выводим в консоль заголовок
console.log(response.headers.get("Content-Type"));
// возвращаем тело ответа
return response.text();
}
})
// в втором then находится ответ на запрос
.then((text) => {
// выведем данные
div.innerHTML = text;
})
// в catch мы попадём когда fetch() вообще не может выполнить запрос, т.е. нет такого сайта или произошла потеря сетевого соединения
.catch((error) => {
console.log(error);
});
});
</script>
</body>
</html>
Можем перебрать заголовки в цикле:
index.html<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>hmarketing.ru</title>
</head>
<body>
<div></div>
<button>Нажми</button>
<script>
let div = document.querySelector("div");
let button = document.querySelector("button");
button.addEventListener("click", function () {
// запрос к файлу ajax.html
let response = fetch("/ajax.html");
response
// в первом then находится информация о запросе и в каком формате мы получим ответ
.then((response) => {
if (response.ok) {
// выводим в консоль заголовки
for (let [key, value] of response.headers) {
console.log(`${key} = ${value}`);
}
// возвращаем тело ответа
return response.text();
}
})
// в втором then находится ответ на запрос
.then((text) => {
// выведем данные
div.innerHTML = text;
})
// в catch мы попадём когда fetch() вообще не может выполнить запрос, т.е. нет такого сайта или произошла потеря сетевого соединения
.catch((error) => {
console.log(error);
});
});
</script>
</body>
</html>
Для получения данных из заголовков мы можем воспользоваться один из следующих методов интерфейса Headers:
entries()возвращает итератор, который позволяет пройтись по всем заголовкамforEach()осуществляет перебор заголовковget()возвращает значение определенного заголовкаhas()возвращаетtrue, если установлен определенный заголовокkeys()получает все названия установленных заголовковvalues()получает все значения установленных заголовков
Заголовки запроса
Для установки заголовка запроса в fetch мы можем использовать опцию headers. Она содержит объект с исходящими заголовками, например:
index.html<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>hmarketing.ru</title>
</head>
<body>
<div></div>
<button>Нажми</button>
<script>
let div = document.querySelector("div");
let button = document.querySelector("button");
button.addEventListener("click", function () {
// запрос к файлу ajax.html
let response = fetch("/ajax.html", {
// устанавливаем заголовок
headers: {
Test: "Test",
},
});
response
// в первом then находится информация о запросе и в каком формате мы получим ответ
.then((response) => {
if (response.ok) {
// возвращаем тело ответа
return response.text();
}
})
// в втором then находится ответ на запрос
.then((text) => {
// выведем данные
div.innerHTML = text;
})
// в catch мы попадём когда fetch() вообще не может выполнить запрос, т.е. нет такого сайта или произошла потеря сетевого соединения
.catch((error) => {
console.log(error);
});
});
</script>
</body>
</html>
Есть список запрещённых HTTP-заголовков, которые мы не можем установить:
Accept-Charset,Accept-EncodingAccess-Control-Request-HeadersAccess-Control-Request-MethodConnectionContent-LengthCookie,Cookie2DateDNTExpectHostKeep-AliveOriginRefererTETrailerTransfer-EncodingUpgradeViaProxy-*Sec-*
Отправка данных в запросе
Для отправки данных в запросе в функции fetch() применяется в рамках второго параметра применяется опция body. Стоит учитывать, что в запросах с методом GET и HEAD для запроса нельзя установить эту опцию. Эти данные могут представлять типы:
BlobBufferSourceFormDataURLSearchParamsUSVStringReadableStream
Определим на странице index.html код для отправки данных на сервер. Для отправки применяется метод POST. В качестве отправляемых данных выступает простая строка Test:
index.html<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>hmarketing.ru</title>
</head>
<body>
<div></div>
<button>Нажми</button>
<script>
let div = document.querySelector("div");
let button = document.querySelector("button");
let login = "test";
let pass = 123123;
button.addEventListener("click", function () {
// запрос к файлу ajax.html
let response = fetch("/ajax.html", {
// устанавливаем заголовок
headers: {
Test: "Test",
},
// устанавливаем метод
method: "POST",
// передаем тело запроса
body: `'login=' + ${login} + '&pass=' + ${pass}`,
});
response
// в первом then находится информация о запросе и в каком формате мы получим ответ
.then((response) => {
if (response.ok) {
// возвращаем тело ответа
return response.text();
}
})
// в втором then находится ответ на запрос
.then((text) => {
// выведем данные
div.innerHTML = text;
})
// в catch мы попадём когда fetch() вообще не может выполнить запрос, т.е. нет такого сайта или произошла потеря сетевого соединения
.catch((error) => {
console.log(error);
});
});
</script>
</body>
</html>
Часто не очень удобно формировать вручную свойство body. В него можно передать объекты класса URLSearchParams, в которых содержатся переменные и их значения:
index.html<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>hmarketing.ru</title>
</head>
<body>
<div></div>
<button>Нажми</button>
<script>
let div = document.querySelector("div");
let button = document.querySelector("button");
let login = "test";
let pass = 123123;
button.addEventListener("click", function () {
// конструктор который передает POST параметры в body
let params = new URLSearchParams();
params.set("login", login);
params.set("pass", pass);
// запрос к файлу ajax.html
let response = fetch("/ajax.html", {
// устанавливаем заголовок
headers: {
Test: "Test",
},
// устанавливаем метод
method: "POST",
// передаем тело запроса
body: params,
});
response
// в первом then находится информация о запросе и в каком формате мы получим ответ
.then((response) => {
if (response.ok) {
// возвращаем тело ответа
return response.text();
}
})
// в втором then находится ответ на запрос
.then((text) => {
// выведем данные
div.innerHTML = text;
})
// в catch мы попадём когда fetch() вообще не может выполнить запрос, т.е. нет такого сайта или произошла потеря сетевого соединения
.catch((error) => {
console.log(error);
});
});
</script>
</body>
</html>
Для кодирования данных метод FormData использует формат "multipart/form-data". Это означает то, что он позволяет подготовить для отправки по AJAX не только текстовые данные, но и файлы (input с type, равным file):
index.html<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>hmarketing.ru</title>
</head>
<body>
<form method="post">
<input type="text" name="user_name" />
<input type="tel" name="user_phone" />
<button>Нажми</button>
</form>
<div></div>
<script>
let div = document.querySelector("div");
let button = document.querySelector("button");
let form = document.querySelector("form");
button.addEventListener("click", function (event) {
// отменяем стандартное действие
event.preventDefault();
// конструктор который создает объект формы
let params = new FormData(form);
// запрос к файлу ajax.html
let response = fetch("/ajax.html", {
// устанавливаем заголовок
headers: {
Test: "Test",
},
// устанавливаем метод
method: "POST",
// передаем тело запроса
body: params,
});
response
// в первом then находится информация о запросе и в каком формате мы получим ответ
.then((response) => {
if (response.ok) {
// возвращаем тело ответа
return response.text();
}
})
// в втором then находится ответ на запрос
.then((text) => {
// выведем данные
div.innerHTML = text;
})
// в catch мы попадём когда fetch() вообще не может выполнить запрос, т.е. нет такого сайта или произошла потеря сетевого соединения
.catch((error) => {
console.log(error);
});
});
</script>
</body>
</html>
Передача куки
По умолчанию куки не передаются в AJAX запросах. Это означает, что не будет работать сессия сервера. Обычно передача кук нам все-таки нужна. Ее можно включить настройкой credentials у которой есть три параметра:
credentials: 'include'заставит передавать куки в AJAX запросе даже если запрос cross-origincredentials: 'same-origin'заставит передавать куки, но только на тот сайт, на котором запускается скриптcredentials: 'omit'запрещает передавать куки
index.html<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>hmarketing.ru</title>
</head>
<body>
<div></div>
<button>Нажми</button>
<script>
let div = document.querySelector("div");
let button = document.querySelector("button");
button.addEventListener("click", function () {
// запрос к файлу ajax.html
let response = fetch("/ajax.html", {
// запрещаем передавать куки
credentials: "omit",
});
response
// в первом then находится информация о запросе и в каком формате мы получим ответ
.then((response) => {
if (response.ok) {
// возвращаем тело ответа
return response.text();
}
})
// в втором then находится ответ на запрос
.then((text) => {
// выведем данные
div.innerHTML = text;
})
// в catch мы попадём когда fetch() вообще не может выполнить запрос, т.е. нет такого сайта или произошла потеря сетевого соединения
.catch((error) => {
console.log(error);
});
});
</script>
</body>
</html>