Статьи

Руководство по ванильному Ajax без jQuery

Сокращенный от Асинхронного JavaScript и XML, Ajax — механизм для частичного обновления страницы. Это позволяет обновлять разделы страницы данными, поступающими с сервера, избегая при этом необходимости полного обновления. Создание частичных обновлений таким способом может быть эффективным для создания гибких пользовательских интерфейсов и может снизить нагрузку на сервер.

Это анатомия основного запроса Ajax:

var xhr = new XMLHttpRequest(); xhr.open('GET', 'send-ajax-data.php'); xhr.send(null); 

Здесь мы создаем экземпляр необходимого класса для отправки HTTP-запроса на сервер. Затем мы вызываем его метод open , указывая метод HTTP-запроса в качестве первого параметра, а URL-адрес запрашиваемой страницы — в качестве второго. Наконец, мы вызываем метод send передавая null в качестве параметра. Если вы отправляете запрос (здесь мы используем GET), этот параметр должен содержать любые данные, которые мы хотим отправить вместе с запросом.

И вот как мы будем иметь дело с ответом от сервера:

 xhr.onreadystatechange = function () { var DONE = 4; // readyState 4 means the request is done. var OK = 200; // status 200 is a successful return. if (xhr.readyState === DONE) { if (xhr.status === OK) { console.log(xhr.responseText); // 'This is the returned text.' } else { console.log('Error: ' + xhr.status); // An error occurred during the request. } } }; 

onreadystatechange является асинхронным, что означает, что он onreadystatechange в любое время. Эти типы функций являются обратными вызовами — те, которые вызываются после завершения обработки. В этом случае обработка происходит на сервере.

Для тех, кто хочет узнать больше об основах Ajax, в сети MDN есть хорошее руководство .

Для jQuery или не для jQuery?

Итак, хорошая новость заключается в том, что приведенный выше код будет работать во всех последних основных браузерах. Плохая новость в том, что она довольно запутанная. Тьфу! Я уже тоскую по элегантному решению.

Используя jQuery, можно сжать весь фрагмент до:

 $.ajax({ url: 'send-ajax-data.php', }) .done(function(res) { console.log(res); }) .fail(function(err) { console.log('Error: ' + err.status); }); 

Что приятно. И действительно, для многих, в том числе и для вас, jQuery стал стандартом де-факто в отношении Ajax. Но знаете что? Это не должно быть так. JQuery существует, чтобы обойти безобразный DOM API. Но разве это так уродливо? Или непонятно?

В оставшейся части этой статьи я хотел бы изучить улучшения, внесенные в Ajax API в vanilla JavaScript. Полная спецификация может быть найдена на W3C . Что поражает меня в этой спецификации, так это название. Это уже не «Уровень 2 XMLHttpRequest», а «Уровень 1 XMLHttpRequest» — результат слияния 2011 года между двумя спецификациями. В дальнейшем он будет рассматриваться как единое целое с точки зрения стандартов, а жизненный стандарт будет называться XMLHttpRequest . Это показывает, что сообщество обязуется придерживаться одного стандарта, и это может означать только хорошие новости для разработчиков, которые хотят освободиться от jQuery.

Итак, начнем …

Настроить

Для этой статьи я использую Node.js на сервере . Да, в браузере и на сервере будет JavaScript. Серверная часть Node.js скудная, я призываю вас загрузить всю демоверсию на GitHub и следить за ней. Вот мясо и картошка того, что на сервере:

 // app.js var app = http.createServer(function (req, res) { if (req.url.indexOf('/scripts/') >= 0) { render(req.url.slice(1), 'application/javascript', httpHandler); } else if (req.headers['x-requested-with'] === 'XMLHttpRequest') { // Send Ajax response } else { render('views/index.html', 'text/html', httpHandler); } }); 

Это проверяет URL-адрес запроса, чтобы определить, как приложение должно отвечать. Если запрос поступил из каталога scripts , то соответствующий файл подается с типом содержимого application/javascript . В противном случае, если для заголовка запроса x-requested-with request x-requested-with установлено значение XMLHttpRequest мы знаем, что имеем дело с запросом Ajax, и можем ответить соответствующим образом. И если ни один из этих случаев не подходит, файл views/index.html обслуживается.

Я расширю закомментированный раздел, когда мы углубимся в ответы Ajax с сервера. В Node.js мне пришлось выполнить некоторые тяжелые действия с render и httpHandler :

 // app.js function render(path, contentType, fn) { fs.readFile(__dirname + '/' + path, 'utf-8', function (err, str) { fn(err, str, contentType); }); } var httpHandler = function (err, str, contentType) { if (err) { res.writeHead(500, {'Content-Type': 'text/plain'}); res.end('An error has occured: ' + err.message); } else { res.writeHead(200, {'Content-Type': contentType}); res.end(str); } }; 

Функция render асинхронно читает содержимое запрошенного файла. httpHandler ссылка на функцию httpHandler , которая затем выполняется как обратный вызов. Функция httpHandler проверяет наличие объекта ошибки (который будет присутствовать, например, если запрошенный файл не может быть открыт). При условии, что все хорошо, он затем передает содержимое файла с соответствующим кодом состояния HTTP и типом содержимого.

Тестирование API

Как и в любом другом звуковом API, давайте напишем несколько модульных тестов, чтобы убедиться, что он работает. Для этих тестов я обращаюсь к supertest и mocha за помощью:

 // test/app.request.js it('responds with html', function (done) { request(app) .get('/') .expect('Content-Type', /html/) .expect(200, done); }); it('responds with javascript', function (done) { request(app) .get('/scripts/index.js') .expect('Content-Type', /javascript/) .expect(200, done); }); it('responds with json', function (done) { request(app) .get('/') .set('X-Requested-With', 'XMLHttpRequest') .expect('Content-Type', /json/) .expect(200, done); }); 

Это гарантирует, что наше приложение отвечает правильным типом контента и кодом состояния HTTP на различные запросы. После того, как вы установили зависимости, вы можете запустить эти тесты из команды, используя npm test .

Интерфейс

Теперь давайте посмотрим на пользовательский интерфейс, который мы создаем в HTML:

 // views/index.html <h1>Vanilla Ajax without jQuery</h1> <button id="retrieve" data-url="/">Retrieve</button> <p id="results"></p> 

HTML выглядит красиво и аккуратно. Как видите, все волнение происходит в JavaScript.

onreadystate против onload

Если вы onreadystate какую-нибудь каноническую книгу по Ajax, вы можете найти onreadystate любом месте. Эта функция обратного вызова поставляется в комплекте с вложенными if и множеством пухов, которые затрудняют запоминание на макушке. Давайте onreadystate события onreadystate и onload на onreadystate .

 (function () { var retrieve = document.getElementById('retrieve'), results = document.getElementById('results'), toReadyStateDescription = function (state) { switch (state) { case 0: return 'UNSENT'; case 1: return 'OPENED'; case 2: return 'HEADERS_RECEIVED'; case 3: return 'LOADING'; case 4: return 'DONE'; default: return ''; } }; retrieve.addEventListener('click', function (e) { var oReq = new XMLHttpRequest(); oReq.onload = function () { console.log('Inside the onload event'); }; oReq.onreadystatechange = function () { console.log('Inside the onreadystatechange event with readyState: ' + toReadyStateDescription(oReq.readyState)); }; oReq.open('GET', e.target.dataset.url, true); oReq.send(); }); }()); 

Это вывод в консоли:

Снимок экрана: вывод консоли с помощью onreadystate и onload

Событие onreadystate происходит повсюду. Он срабатывает в начале каждого запроса, в конце, а иногда просто потому, что ему действительно нравится, когда его увольняют. Но в соответствии со спецификацией событие onload срабатывает только при onload выполнении запроса. Таким образом, событие onload — это современный API, который можно эффективно использовать за считанные секунды. Событие onreadystate должно быть обратно совместимым. Но событие onload должно быть вашим выбором. onload похоже на success обратный вызов в jQuery, не так ли?

Пришло время отложить 5-фунтовые гантели в сторону и перейти к рукам.

Установка заголовков запроса

jQuery устанавливает заголовки запросов под крышками, чтобы ваша внутренняя технология знала, что это Ajax-запрос. Как правило, бэкэнд не заботится о том, откуда поступает GET-запрос, если он отправляет правильный ответ. Это удобно, когда вы хотите поддерживать Ajax и HTML с одним и тем же веб-API. Итак, давайте посмотрим, как установить заголовки запроса в ванильном Ajax:

 var oReq = new XMLHttpRequest(); oReq.open('GET', e.target.dataset.url, true); oReq.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); oReq.send(); 

С этим мы можем сделать проверку в Node.js:

 if (req.headers['x-requested-with'] === 'XMLHttpRequest') { res.writeHead(200, {'Content-Type': 'application/json'}); res.end(JSON.stringify({message: 'Hello World!'})); } 

Как видите, vanilla Ajax — это гибкий и современный интерфейсный API. Существует множество идей, для которых вы можете использовать заголовки запросов, и одна из них — управление версиями. Например, допустим, я хочу поддерживать более одной версии этого веб-API. Это полезно, когда я не хочу ломать URL-адреса и вместо этого предоставляю механизм, в котором клиенты могут выбирать версию, которую они хотят. Мы можем установить заголовок запроса так:

 oReq.setRequestHeader('x-vanillaAjaxWithoutjQuery-version', '1.0'); 

А в конце попробуйте:

 if (req.headers['x-requested-with'] === 'XMLHttpRequest' && req.headers['x-vanillaajaxwithoutjquery-version'] === '1.0') { // Send Ajax response } 

Node.js предоставляет вам объект headers вы можете использовать для проверки заголовков запросов. Единственный трюк в том, что он читает их строчными буквами.

Мы дома растянулись и не потели! Вы можете быть удивлены, что еще можно узнать об Ajax? Ну, как насчет пары изящных трюков.

Типы ответов

Вы можете быть удивлены, почему responseText содержит responseText сервера, когда все, с чем я работаю, это просто старый JSON. Оказывается, это потому, что я не установил правильный reponseType . Этот атрибут Ajax отлично подходит для того, чтобы сообщить интерфейсному API, какой тип ответа ожидать от сервера. Итак, давайте использовать это для хорошего использования:

 var oReq = new XMLHttpRequest(); oReq.onload = function (e) { results.innerHTML = e.target.response.message; }; oReq.open('GET', e.target.dataset.url, true); oReq.responseType = 'json'; oReq.send(); 

Круто, вместо того, чтобы отправлять обратно простой текст, который мне потом нужно проанализировать в JSON, я могу сказать API, чего ожидать. Эта функция доступна практически во всех последних основных браузерах. JQuery, конечно, делает этот тип преобразования автоматически. Но разве не здорово, что теперь у нас есть удобный способ сделать то же самое в простом JavaScript? Vanilla Ajax поддерживает многие другие типы ответов, включая XML.

К сожалению, в Internet Explorer история не так удивительна. Начиная с IE 11, команда еще не добавила поддержку xhr.responseType = ‘json’ . Эта функция должна появиться в Microsoft Edge . Но ошибка была выдающейся почти два года на момент написания. Я думаю, что люди в Microsoft усердно работали над обновлением браузера. Будем надеяться, что Microsoft Edge, он же Project Spartan, выполняет свои обещания.

Увы, если вам нужно обойти эту проблему IE:

 oReq.onload = function (e) { var xhr = e.target; if (xhr.responseType === 'json') { results.innerHTML = xhr.response.message; } else { results.innerHTML = JSON.parse(xhr.responseText).message; } }; 

Кэш Разорение

Одна из особенностей браузера, которую люди часто забывают, — это возможность кэширования запросов Ajax. Internet Explorer, например, делает это по умолчанию. Однажды я боролся часами, пытаясь понять, почему мой Ajax не работал из-за этого. К счастью, jQuery по умолчанию отключает кеш браузера. Ну, вы тоже можете на простом Ajax, и это довольно просто:

 var bustCache = '?' + new Date().getTime(); oReq.open('GET', e.target.dataset.url + bustCache, true); 

В соответствии с документацией jQuery все, что он делает, это добавляет строку запроса временной метки в конец запроса. Это делает запрос несколько уникальным и разрушает кеш браузера. Вы можете увидеть, как это выглядит при запуске HTTP-запросов Ajax:

Вывод на консоль, показывающий, как кэширование работает

Тада! Все без драмы.

Вывод

Надеюсь, вам понравился 300-фунтовый ванильный жим Ajax. Однажды Аякс был ужасным зверем, но не более того. Фактически, мы рассмотрели все основы Ajax без костылей, кандалов, jQuery.

Я оставлю вам краткий способ делать Ajax-звонки:

 var oReq = new XMLHttpRequest(); oReq.onload = function (e) { results.innerHTML = e.target.response.message; }; oReq.open('GET', e.target.dataset.url + '?' + new Date().getTime(), true); oReq.responseType = 'json'; oReq.send(); 

И вот как выглядит ответ:

Скриншот ответа Ajax

Не забывайте, вы можете найти всю демоверсию на GitHub . Я хотел бы услышать ваши мысли Ajax с и без jQuery в комментариях.