XMLHttpRequest против Fetch API: что лучше для отправки Ajax в 2019 году?
В марте 2019 года будет юбилей, 20-летие Ajax. Первая реализация XMLHttpRequest была выпущена в 1999 году как компонент ActiveX IE5.0.
До этого существовали способы извлечения данных с сервера без полного обновления страницы, но они часто полагались на неуклюжие методы, такие как внедрение script или сторонние плагины. Microsoft разработала первичную версию XMLHttpRequest для браузерной альтернативы своему почтовому клиенту Outlook.
XMLHttpRequest не был веб-стандартом до 2006 года, но он был реализован в большинстве браузеров. Его принятие в Gmail (2004) и Google Maps (2005) привело к появлению в 2005 году статьи Джесси Джеймса Гарретта AJAX: новый подход к веб-приложениям. Новый термин кристаллизуется в центре внимания разработчиков.
AJAX для Ajax
AJAX - это мнемоника Asynchronous JavaScript and XML. “Асинхронный” определенно, но:
- Выбран JavaScript, хотя были и другие варианты, VBScript и Flash
- Полезная нагрузка не должна была быть в формате XML, хоть это и было популярно в то время. Сегодня обычно предпочтительнее использовать JSON.
Теперь мы используем «Ajax» в качестве общего термина для любого процесса на стороне клиента, который извлекает данные с сервера и динамически обновляет DOM без полного обновления страницы. Ajax - это основной метод для большинства веб-приложений и одностраничных приложений (SPA).
Экстремальный XMLHttpRequest
Следующий код JavaScript показывает базовый HTTP-запрос GET для http://domain/service с использованием XMLHttpRequest (обычно сокращается до XHR):
let xhr = new XMLHttpRequest(); xhr.open('GET', 'http://domain/service'); // Подписываемся на изменение состояния запроса xhr.onreadystatechange = function() { // Проверка, завершилось ли выполнение запроса? if (xhr.readyState !== 4) return; if (xhr.status === 200) { // Обработка удачного выполнения запроса console.log(xhr.responseText); } else { // Обработка ошибки console.log('HTTP error', xhr.status, xhr.statusText); } }; // Отправляем запрос xhr.send();
Объект XMLHttpRequest имеет много других параметров, событий и свойств ответа. Например, тайм-аут в миллисекундах может быть установлен и обнаружен:
// установка timeout xhr.timeout = 3000; // 3 секунды xhr.ontimeout = () => console.log('timeout', xhr.responseURL);
и событие progress может сообщить о длительной загрузке файла:
xhr.upload.onprogress = p => { console.log( Math.round((p.loaded / p.total) * 100) + '%') ; }
Число параметров может вызывать недоумение, и ранние реализации XMLHttpRequest имели несколько кросс-браузерных несоответствий. По этой причине большинство библиотек и сред предлагают функции-оболочки Ajax для решения этих проблем, например, метод jQuery.ajax():
$.ajax('http://domain/service') .done(data => console.log(data)) .fail((xhr, status) => console.log('error:', status));
Быстрая перемотка Fetch
Fetch API является современной альтернативой XMLHttpRequest. Универсальные интерфейсы Headers, Request и Response обеспечивают согласованность, в то время как Promises позволяют упростить цепочки и async/await без обратных вызовов. Приведенный выше пример XHR можно преобразовать в гораздо более простой код на основе Fetch, который даже анализирует возвращенный JSON:
fetch( 'http://domain/service', { method: 'GET' } ) .then( response => response.json() ) .then( json => console.log(json) ) .catch( error => console.error('error:', error) );
Fetch - чистый, элегантный, простой для понимания и интенсивно используемый в PWA Service Workers. Почему бы вам не использовать его вместо древнего XMLHttpRequest?
К сожалению, веб-разработка никогда не бывает такой четкой. Fetch еще не является полноценной заменой методов Ajax…
Поддержка браузера
Fetch API достаточно хорошо поддерживается, но он не будет работать во всех выпусках Internet Explorer. Люди, использующие версии Chrome, Firefox и Safari старше 2017 года, также могут испытывать проблемы. Эти пользователи могут составлять небольшую часть ваших пользователей... или это может быть основной клиент. Всегда проверяйте, прежде чем начать кодирование!
Cookieless по умолчанию
В отличие от XMLHttpRequest, не все реализации Fetch будут отправлять куки-файлы, поэтому аутентификация вашего приложения может быть неудачной. Проблема может быть исправлена путем изменения параметров инициации, передаваемых во втором аргументе, например:
fetch('http://domain/service', { method: 'GET', credentials: 'same-origin' })
Ошибки не отклоняются
Удивительно, но ошибка HTTP, такая как 404 Page Not Found или 500 Internal Server Error, .catch() никогда не запускается. Обычно он разрешается с состоянием response.ok, установленным в false.
Отказ происходит только в том случае, если запрос не может быть выполнен, например, сбой сети. Это может усложнить реализацию перехвата ошибок.
Тайм-ауты не поддерживаются
Fetch не поддерживает тайм-ауты, и запрос будет продолжаться до тех пор, пока браузер его обрабатывает. Для того, чтобы обернуть выборку в другое обещание, требуется дополнительный код, например:
function fetchTimeout(url, init, timeout = 3000) { return new Promise((resolve, reject) => { fetch(url, init) .then(resolve) .catch(reject); setTimeout(reject, timeout); } }
…или, возможно, используйте Promise.race()
Promise.race([ fetch('http://url', { method: 'GET' }), new Promise(resolve => setTimeout(resolve, 3000)) ]).then(response => console.log(response))
Отмена в Fetch
Запрос XHR легко завершить с помощью xhr.abort() и, при необходимости, обнаружить такое событие с помощью функции xhr.onabort.
Прекращение отправки было невозможно в течение нескольких лет, но теперь оно поддерживается в браузерах, которые реализуют API AbortController. Это запускает сигнал, который может быть передан объекту инициации Fetch:
const controller = new AbortController();fetch('http://domain/service', { method: 'GET' signal: controller.signal }) .then( response => response.json() ) .then( json => console.log(json) ) .catch( error => console.error('Error:', error) );
Обработка может быть прервана вызовом controller.abort(). Promise отклоняет, поэтому вызывается функция .catch().
Нет прогресса
На момент написания, Fetch не поддерживает события прогресса. Поэтому невозможно сообщить о статусе загрузки файлов или аналогичных представлений больших форм.
XMLHttpRequest против Fetch API?
В конечном счете, выбор за вами... если только у вашего приложения нет клиентов использующих IE, которым требуются индикаторы загрузки.
Для простых вызовов Ajax, XMLHttpRequest является более низким уровнем, более сложным, и вам потребуются функции-оболочки. К сожалению, то же самое произойдет и после того, как вы начнете учитывать сложность тайм-аутов, прерываний вызовов и отслеживания ошибок.
Вы можете выбрать Fetch полифил вместе с полифилом Promise, чтобы можно было писать код Fetch в IE. Тем не менее, XHR используется как запасной вариант; не каждый вариант будет работать так, как ожидалось, например, куки будут отправлены независимо от настроек.
Fetch это будущее. Тем не менее, API является относительно новым, он не обеспечивает все функциональные возможности XHR, а некоторые параметры являются громоздкими. Используйте его с осторожностью в течение следующих нескольких лет.