HTML / CSSJavaScriptNode jsПаттерны проектированияПрактические

Ajax

Что такое AJAX?

AJAX — это аббревиатура, которая означает Asynchronous Javascript and XML. На самом деле, AJAX не является новой технологией, так как и Javascript, и XML существуют уже довольно продолжительное время, а AJAX — это синтез обозначенных технологий. AJAX чаще всего ассоциируется с термином Web 2.0 и преподносится как новейшее Web-приложение.

При использовании AJAX нет необходимости обновлять каждый раз всю страницу, так как обновляется только ее конкретная часть. Это намного удобнее, так как не приходится долго ждать, и экономичнее, так как не все обладают безлимитным интернетом. Правда в этом случае, разработчику необходимо следить, чтобы пользователь был в курсе того, что происходит на странице. Это можно реализовать с использованием индикаторов загрузки, текстовых сообщений о том, что идёт обмен данными с сервером. Необходимо также понимать, что не все браузеры поддерживают AJAX (старые версии браузеров и текстовые браузеры). Плюс Javascript может быть отключен пользователем. Поэтому, не следует злоупотреблять использованием технологии и прибегать к альтернативным методам представления информации на Web-сайте.

Обобщим достоинства AJAX:

  • Возможность создания удобного Web-интерфейса
  • Активное взаимодействие с пользователем
  • Частичная перезагрузка страницы, вместо полной
  • Удобство использования

AJAX использует два метода работы с веб-страницей: изменение Web-страницы не перезагружая её, и динамическое обращение к серверу. Второе может осуществляться несколькими способами, в частности, XMLHttpRequest, о чем мы и будем говорить, и использование техники скрытого фрейма.

Что можно делать с помощью AJAX?

Элементы интерфейса

В первую очередь AJAX полезен для форм и кнопок, связанных с элементарными действиями: добавить в корзину, подписаться, и т.п.

Сейчас – в порядке вещей, что такие действия на сайтах осуществляются без перезагрузки страницы.

Динамическая подгрузка данных

Например, дерево, которое при раскрытии узла запрашивает данные у сервера.

Живой поиск

Живой поиск – классический пример использования AJAX, взятый на вооружение современными поисковыми системами.

Пользователь начинает печатать поисковую фразу, а JavaScript предлагает возможные варианты, получая список самых вероятных дополнений с сервера.

Код, который это обеспечивает, работает следующим образом.

Код активируется примерно при каждом нажатии клавиши, но не чаще чем раз в 100 мс (примерно). 2. Создаётся скрытый DIV и заполняется ответом сервера:

  • Текущий результат подсвечен, можно перемещаться и выбирать
  • При нажатии правой стрелки или при клике -- поиск в подрезультатах
  1. Результаты запросов кешируются, повторных обращений к серверу не происходит.
  2. В Google не только предлагаются варианты, но система тут же инициирует и сам поиск, т.е. не нужно даже нажимать key:Enter. Технически, с помощью AJAX можно обмениваться любыми данными с сервером.

Обычно используются форматы:

JSON – для отправки и получения структурированных данных, объектов. XML – если сервер почему-то работает в формате XML, то можно использовать и его, есть средства. HTML/текст – можно и просто загрузить с сервера код HTML или текст для показа на странице. Бинарные данные, файлы – гораздо реже, в современных браузерах есть удобные средства для них.

XMLHttpRequest

XMLHttpRequest – это встроенный в браузер объект, который даёт возможность делать HTTP-запросы к серверу без перезагрузки страницы.

Несмотря на наличие слова «XML» в названии, XMLHttpRequest может работать с любыми данными, а не только с XML. Мы можем загружать/скачивать файлы, отслеживать прогресс и многое другое.

❗️Важно

На сегодня не обязательно использовать XMLHttpRequest, так как существует другой, более современный метод fetch.

В современной веб-разработке XMLHttpRequest используется по трём причинам:

По историческим причинам: существует много кода, использующего XMLHttpRequest, который нужно поддерживать.

  • Необходимость поддерживать старые браузеры, и нежелание использовать полифилы (например, чтобы уменьшить количество кода).
  • Потребность в функциональности, которую fetch пока что не может предоставить, к примеру, отслеживание прогресса отправки на сервер.
  • Что-то из этого списка звучит знакомо? Если да, тогда вперёд, приятного знакомства с XMLHttpRequest. Если же нет, возможно, имеет смысл изучать сразу Fetch.

XMLHttpRequest - имеет два режима работы: синхронный и асинхронный.

Асинхронный режим:

Чтобы сделать запрос, нам нужно выполнить следующее:

1// 1. Создаем экземпляр XMLHttpRequest.
2let xhr = new XMLHttpRequest();
3// 2. Инициализировать его.
4xhr.open(method, URL, [async, user, password])
5// 3. Шлем запрос
6xhr.send([body])
7
8// 4. Слушать события на xhr, чтобы получить ответ.
9
10xhr.onload = function() {
11 alert(`Загружено: ${xhr.status} ${xhr.response}`);
12};
13
14// происходит, только когда запрос совсем не получилось выполнить
15xhr.onerror = function() {
16 alert(`Ошибка соединения`);
17};
18// запускается периодически
19xhr.onprogress = function(event) {
20 // event.loaded - количество загруженных байт
21 // event.lengthComputable = равно true, если сервер присылает заголовок Content-Length
22 // event.total - количество байт всего (только если lengthComputable равно true)
23 alert(`Загружено ${event.loaded} из ${event.total}`);
24};

xhr.open

Метод обычно вызывается сразу после new XMLHttpRequest. В него передаются основные параметры запроса:

  • method – HTTP-метод. Обычно это "GET" или "POST".
  • URL – URL, куда отправляется запрос: строка, может быть и объект URL.
  • async – если указать false, тогда запрос будет выполнен синхронно.
  • user, password – логин и пароль для базовой HTTP-авторизации (если требуется).
ℹ️Примечание

Вызов open, вопреки своему названию, не открывает соединение. Он лишь конфигурирует запрос, но непосредственно отсылается запрос только лишь после вызова send.

xhr.send([body])

Метод устанавливает соединение и отсылает запрос к серверу. Необязательный параметр body содержит тело запроса.

Некоторые типы запросов, такие как GET, не имеют тела. А некоторые, как, например, POST, используют body, чтобы отправлять данные на сервер.

Три наиболее используемых события xhr:

  • load – происходит, когда получен какой-либо ответ, включая ответы с HTTP-ошибкой, например 404.
  • error – когда запрос не может быть выполнен, например, нет соединения или невалидный URL.
  • progress – происходит периодически во время загрузки ответа, сообщает о прогрессе.

Полный пример получения списка стран с помощью XMLHttpRequest

Original source

После ответа сервера мы можем получить результат запроса в следующих свойствах xhr:

status - Код состояния HTTP (число): 200, 404, 403 и так далее, может быть 0 в случае, если ошибка не связана с HTTP. statusText - Сообщение о состоянии ответа HTTP (строка): обычно OK для 200, Not Found для 404, Forbidden для 403, и так далее. response - в старом коде может встречаться как responseText

Тип ответа

Мы можем использовать свойство xhr.responseType, чтобы указать ожидаемый тип ответа:

  • "" (по умолчанию) – строка,
  • "text" – строка,
  • "arraybuffer" – ArrayBuffer (для бинарных данных, смотрите в ArrayBuffer, бинарные массивы),
  • "blob" – Blob (для бинарных данных, смотрите в Blob),
  • "document" – XML-документ (может использовать XPath и другие XML-методы),
  • "json" – JSON (парсится автоматически).
1//Создаём новый XMLHttpRequest-объект
2 const xhr = new XMLHttpRequest();
3
4 //Настраиваем его: GET-запрос по URL https://restcountries.eu/rest/v2/all
5 xhr.open("GET", "https://restcountries.eu/rest/v2/all");
6
7 // Отсылаем запрос
8 xhr.send();
9
10 xhr.responseType = "json";
11
12xhr.onload = function() {
13 const countires = xhr.response;
14 alert(countires.length); // выведет количество стран
15};

Состояния запроса

У XMLHttpRequest есть состояния, которые меняются по мере выполнения запроса. Текущее состояние можно посмотреть в свойстве xhr.readyState.

Список всех состояний, указанных в спецификации:

1UNSENT = 0; // исходное состояние
2OPENED = 1; // вызван метод open
3HEADERS_RECEIVED = 2; // получены заголовки ответа
4LOADING = 3; // ответ в процессе передачи (данные частично получены)
5DONE = 4; // запрос завершён

Состояния объекта XMLHttpRequest меняются в таком порядке: 0 → 1 → 2 → 3 → … → 3 → 4. Состояние 3 повторяется каждый раз, когда получена часть данных.

Изменения в состоянии объекта запроса генерируют событие readystatechange:

1xhr.onreadystatechange = function() {
2 if (xhr.readyState === 3) {
3 // загрузка
4 }
5 if (xhr.readyState === 4) {
6 // запрос завершён
7 }
8};

Отмена запроса

Если мы передумали делать запрос, можно отменить его вызовом xhr.abort():

xhr.abort(); // завершить запрос

При этом генерируется событие abort, а xhr.status устанавливается в 0.

Синхронные запросы

Если в методе open третий параметр async установлен на false, запрос выполняется синхронно.

Другими словами, выполнение JavaScript останавливается на send() и возобновляется после получения ответа. Так ведут себя, например, функции alert или prompt.

Синхронный код на получение списка стран с сервера:

1const xhr = new XMLHttpRequest();
2 xhr.open("GET", "https://restcountries.eu/rest/v2/all",false);
3try {
4 xhr.send();
5 if (xhr.status !== 200) {
6 alert(`Ошибка ${xhr.status}: ${xhr.statusText}`);
7 } else {
8 alert(xhr.response);
9 }
10} catch(err) { // для отлова ошибок используем конструкцию try...catch вместо onerror
11 alert("Запрос не удался");
12}
❗️Важно

Синхронные запросы используются редко, так как они блокируют выполнение JavaScript до тех пор, пока загрузка не завершена. В некоторых браузерах нельзя прокручивать страницу, пока идёт синхронный запрос.

Fetch

Метод fetch() — современный и очень мощный, поэтому начнём с него. Он не поддерживается старыми (можно использовать полифил), но поддерживается всеми современными браузерами.

Базовый синтаксис:

1let promise = fetch(url, [options])
  • url – URL для отправки запроса.
  • options – дополнительные параметры: метод, заголовки и так далее.

Без options это простой GET-запрос, скачивающий содержимое по адресу url. Браузер сразу же начинает запрос и возвращает promise, который внешний код использует для получения результата. Процесс получения ответа обычно происходит в два этапа.

Во-первых, promise выполняется с объектом встроенного класса Response в качестве результата, как только сервер пришлёт заголовки ответа.

На этом этапе мы можем проверить статус HTTP-запроса и определить, выполнился ли он успешно, а также посмотреть заголовки, но пока без тела ответа. Promise завершается с ошибкой, если fetch не смог выполнить HTTP-запрос, например при ошибке сети или если нет такого сайта. HTTP-статусы 404 и 500 не являются ошибкой.

Мы можем увидеть HTTP-статус в свойствах ответа:

status – код статуса HTTP-запроса, например 200. ok – логическое значение: будет true, если код HTTP-статуса в диапазоне 200-299.

1fetch(url).then( function(response){
2 if (response.ok) { // если HTTP-статус в диапазоне 200-299
3 // получаем тело ответа (см. про этот метод ниже)
4 return response.json();
5} else {
6 alert("Ошибка HTTP: " + response.status);
7}
8
9})
10.then(function(result) {
11 console.log('result',result)
12});

Для получения тела ответа нам нужно использовать дополнительный вызов метода.

Response предоставляет несколько методов, основанных на промисах, для доступа к телу ответа в различных форматах:

  • response.text() – читает ответ и возвращает как обычный текст,
  • response.json() – декодирует ответ в формате JSON,
  • response.formData() – возвращает ответ как объект FormData (разберём его в следующей главе),
  • response.blob() – возвращает объект как Blob (бинарные данные с типом),
  • response.arrayBuffer() – возвращает ответ как ArrayBuffer (низкоуровневое представление бинарных данных),

помимо этого, response.body – это объект ReadableStream, с помощью которого можно считывать тело запроса по частям.

Например, получим JSON-объект со списком стран:

1const url = "https://restcountries.eu/rest/v2/all";
2fetch(url).then(function(response) {
3return response.json()
4}).then(function(countries) {
5 alert(countries[0].name);
6});

Полный пример получения списка стран с помощью Fetch

Hello