Basics: Using AJAX with Fetch API
Today we want interactive websites. So after a website is loaded we often need to save something to the server or get new data from it. For that we don’t want to always reload the page so we need something to connect to a server, an API, through JavaScript. That’s what AJAX is for.
AJAX stands for Asynchronous JavaScript And XML. AJ represents that code is executed asynchronously, and XML distributes data over the internet through browsers. The term AJAX is a bit outdated, as we don’t often use XML anymore.
Originally the XMLHttpRequest API was the working standard for many years until other APIs were created to simplify the code confusion. The Fetch API provides a JavaScript interface for accessing and manipulating parts of the HTTP pipeline for requests and responses using promises. Fetch also provides a global fetch() method that logically and simply fetches resources asynchronously across the network.
To show the difference with the first API called XMLHttpRequest and Fetch, check the following example:
And here is the example using the Fetch API:
Check out another example explained in detail:
Here are some other useful methods that you can use with the Fetch API:
- clone(): creates a clone of the response
- redirect(): creates a new response but with a different URL
- arrayBuffer(): returns a promise that resolves with an array buffer
- formData(): returns a promise that resolves with a form data object
- blob(): resolves with a blob
- text(): resolves with a string
- json(): resolves the promise with JSON
Fetch is a great API to use for asynchronous execution of code. It is however created for ECMA script 6, but now most browsers are updated to using ES6, so it’s good apart from the Opera browsers. Personally, I’d much rather use fetch than the XMLHttpRequest API because I prefer simple and easy to read code.
Making HTTP Requests from JavaScript (AJAX)
Now that you know how to create page content from data stored in a local variable, the next logical step is to learn how to fetch those data dynamically from a web service. This allows you to get up-to-date data each time your page loads, and even automatically refresh those data while the user remains on the page. JavaScript allow us to make requests to other web services, and process the results, without navigating or refreshing the page.
Back in the early days of the web (early 1990s), all data fetching and template merging happened on the server. Web browsers received fully-merged static HTML pages, and if you wanted fresher data, you had to refresh the page. If you wanted to send data back to the server, you filled out a form and let the browser submit the form to the server, where the data were processed and a new response page was generated. At that time, browsers were more like the «dumb terminals» of the mini-computer era, and all the real work happened on the server-side.
JavaScript was added to web browsers starting in 1995, which made it possible to execute code on the client-side. But that code couldn’t do very much until 1997, when both Netscape Navigator and Internet Explorer added the Document Object Model (DOM). Now one could conceivably build page content from data, but the data still had to be included in the initial set of files downloaded for the page. Refreshing the data still required a full page refresh.
In the late 1990s, Microsoft started building a web-based version of their email and calendaring application Outlook, and they wanted a way to check for new mail and send new messages without triggering a full page refresh. Initially they toyed around with using a hidden <iframe> element, but the technique was clumsy and full of potential security issues. So they eventually proposed a new programming interface that would let JavaScript make an asynchronous HTTP request back to the web server from which the page came, and process the results once they returned. In response, the Internet Explorer team added a new global object named XMLHttpRequest , which could do just that.
The name of the object began with XML because at that time XML was all the rage, and the Outlook Web Access team figured they would encode data transmitted from the server to the client in XML. The XMLHttpRequest object would let them make an HTTP request to their server, and would automatically parse the returned data as XML so that they could work with it in their JavaScript. Since HTML was a variant of XML, they could use the same DOM interface when working with the returned data.
When I first saw this in action, my jaw hit the floor. Up until that point, the web was a pretty mediocre application platform, barely better than what we had in the 1980s with mini-computers and dumb terminals. Now with the XMLHttpRequest object, we could build rich, interactive graphical client-side applications, like we had been building on desktop GUI operating systems like Windows, but with no installation step required. A user could simply navigate to a web site, and start using the downloaded application.
This technique quickly became popular, and was given a name: AJAX, which was an acronym for Asynchronous JavaScript And XML. The «Asynchronous» part came from the fact that HTTP requests needed to be done asynchronously so that they didn’t block the rest of your JavaScript. Since JavaScript is single-threaded, they couldn’t just pause all of your script while waiting for the server to respond (which could take a while on a slow network). If the user clicked something in the meantime and your click event listener function didn’t run, the user would think that the application was broken. So the HTTP request is done asynchronously, and your code provides a callback function, which is invoked once the server responds.
By the mid-2000s, all major web sites were adding AJAX-based functionality, and new web applications were architected entirely around AJAX. These new application architectures were more reminiscent of the client-server era, where servers exposed Application Programming Interfaces (APIs) that returned and accepted raw data, encoded in some sort of easily interchanged format. Transforming that data into something a user could see was the job of the client application, and one could build many types of client applications against the same server. Since only data flowed back and forth over the network and not a presentation language like HTML, one could build both a web client and a native desktop client that spoke to the same server. As soon as smart mobile devices came along, they became just one more type of client application, interacting with the same server APIs.
In a client-server architecture, the client and server applications are commonly implemented in different programming languages, so the data that is sent between them must be encoded into a format that is easy to generate and parse by all languages. In the late-1990s and early-2000s, the encoding format of choice was XML, but JavaScript developers became frustrated with how difficult it was to work with XML data in the web browser. In contrast, JavaScript arrays and objects were very easy to work with, and there was already a simple text-based format for declaring those that any language could consume, so why not use that instead?
The result was the JSON encoding scheme, which stands for JavaScript Object Notation. The format is much simpler and more compact than XML, and it can be parsed directly into native JavaScript arrays and objects. It also supports a wider set of value literals—all attributes and element content in XML must be encoded as a string so extra meta-data is required to indicate the actual data type (number, boolean, etc). JSON defines a syntax for literal strings, numbers, booleans, and nulls.
JSON was also very familiar to JavaScript developers, as it looks almost identical to the way one declares arrays and objects in code. For example, here is a JSON-encoded object:
The only real difference between JSON and a JavaScript object declaration is that the property names must be wrapped in quotes, even if they are legal JavaScript identifiers. In a JavaScript object declaration, we can omit the quotes around property names that are legal identifiers, but it also doesn’t hurt to include the quotes.
Note that the value for the «id» property is a literal number, so it’s included without quotes. This tells any program parsing this data that the value is numeric and should be parsed as such. Similarly, the value for «active» is a boolean literal true , which will be parsed into a boolean value (as opposed to a string or a number). And the value for «description» is a literal null , which will be parsed into a null data type.
JavaScript has built-in support for generating and parsing JSON. To parse the JSON above, you can use JSON.parse() :
And to generate JSON, you can use JSON.stringify() :
JSON quickly displaced XML as the data-encoding format of choice for web applications, and parsers were developed for all major programming languages. We still refer to the general technique as «AJAX», but it’s rare to find a web application these days that uses XML as the primary data-encoding format.
The fetch() Function
The XMLHttpRequest object allows JavaScript developers to request JSON or XML-encoded data from the server, but the programming interface it exposed is quite complex to use. For example, here is the code necessary to make a simple request for some data:
That’s quite a lot of code for a rather simple operation. Understandably, developers quickly built libraries to simplify the common case. The most used was the .getJSON() method that was added to the popular jQuery library. It replaced the code above with this:
This was a much easier programming interface, but it required the entire jQuery library to be included in the page, which added another 87KB of script to your application. And since the need for the jQuery library has largely gone away, developers started asking for something like the jQuery .getJSON() method to be built-in to the browser as a native API.
The result was the fetch() API, which is now supported in all major browsers except IE 11 and Safari 10. For those browsers, we just have to add a small polyfill library that implements the fetch() function using the existing XMLHttpRequest object.
A polyfill library is one that adds a feature that is not yet supported natively by the browser by implementing that feature in JavaScript using existing functionality. The fetch polyfill implements the new fetch() function using the existing XMLHttpRequest interface. If the library detects the native implementation, it simply exits and does nothing.
To add the fetch() polyfill library from its CDN, simply include this script element in your HTML page, before any script where you use the fetch() function:
Asynchronous Requests and Promises
Because HTTP requests are done asynchronously, the fetch() function returns an object known as a Promise. A Promise represents an asynchronous operation that will eventually complete successfully or fail. A Promise allows developers to add functions that should be called when either of those conditions occurs. These are just like event listeners, except there are only two events: success (aka resolve), and fail (aka reject). So the Promise object provides two different methods, one for registering a success listener ( .then() ), and one for registering a failure listener ( .catch() ).
To fetch a URL, call the fetch() function passing the URL you want to fetch. It returns a Promise, which you can use to register a callback function that will be invoked when the server responds:
Declaring that intermediate variable promise is unnecessary in JavaScript, so we typically combine those two statements above into one:
And to make the code a bit more readable, we typically insert a line break after the call to fetch() and before the call to the Promise’s .then() method:
The function we pass to the Promise’s .then() method will be called once the server begins to respond. The response object passed to this function as the first parameter allows us to do several things, but most commonly, you will want to parse the response body as JSON-encoded data. To do that, simply return response.json() .
Some APIs you might call return plain text instead of JSON-encoded data. In that case, use return response.text() instead. This method reads the response body, but doesn’t try to parse it as JSON. Instead, it simply reads the response body into a string and passes that to the next .then() callback function.
Both the response.json() and response.text() methods are asynchronous operations, so they actually return a new Promise as well. But the neat thing about promises is that if you return a new Promise from a .then() callback function, the outer Promise will take on the state of the new returned Promise. That allows us to add another .then() callback function, which will be called once the reading/JSON-parsing has completed:
The return value of the first .then() method is the same Promise object, so syntactically we can keep chaining .then() methods, one after the other.
All of this code so far assumes that the network request completes successfully, but when computer networks are involved, you should always assume that the request could fail. The client’s WiFi connection might be down, or their connection to their ISP could be down, or the server could be down.
To respond to a network failure, we can use the Promise’s .catch() method to register a function that will be called if the request fails. Your function will be handed an Error object as the first parameter, which will contain details about the error.
The really nice thing about Promises is that the function we pass to .catch() will be called if any error occurs either during the fetch, or in the JSON parsing. If any of the functions passed to .then() cause an error to occur, execution will automatically jump to your .catch() function, skipping any intermediary .then() functions. This allows you to centralize your error handling in one place, and avoid calling code that depends upon the previous code executing without errors.
Checking the Response Status Code
The promise returned by fetch() will be rejected only if there was a problem communicating with the server (e.g., invalid domain name, network error, etc.). But if you are able to communicate with the server, it still might respond with an error if you omit something that is required, or if the server experiences some sort of internal error. In these cases, the server responds with a status code that is >= 400. The important thing to remember is that these error responses do not cause the Promise to be rejected: as far as fetch() is concerned, the request was successfully transmitted and the response was successfully received. It’s up to you to check for these error status codes, and handle them appropriately.
Thankfully it’s pretty easy to do this. The response object passed to your first callback function has a property named ok that is set to false if the response status code is >= 400. If that property is false , you can read the response body (which should contain some sort of error message) and throw a new JavaScript Error , using the response body text as the error message:
As noted above, if any of your .then() callback functions throws an error, the Promise will become rejected, and thus call your next .catch() callback function, passing the error that was thrown. In the example above, if response.ok is false , the second .then() callback function is never invoked because the first one throws a new JavaScript Error . Instead, execution jumps to the .catch() callback function, where the error is handled.
Trying it Out
There are many, many sources of live data on the web, but the one we will use for a quick demo, and for your challenge, is the data.seattle.gov site. This site hosts public data for the city of Seattle, and one interesting dataset is the hourly bicycle traffic counts across the Fremont bridge.
All of the datasets on the site can be returned in JSON format, and you can supply extra parameters on the URL to filter and sort the data. For example, this URL will return the most-recent 24 hours of data. Click on the URL to see the JSON data in your browser.
You can see that it returns an array of objects, one for each hour. The objects have three properties each: date (date and time of observation), fremont_bridge_nb (number of bikes traveling on the east sidewalk), and fremont_bridge_sb (number of bikes traveling on the west sidewalk).
Once we fetch this data and parse it as JSON, we can render it to an HTML table, similar to how to rendered the people array in the previous tutorial, or the MOVIES array in the previous challenge.
Использование Fetch
Fetch API предоставляет интерфейс JavaScript для работы с запросами и ответами HTTP. Он также предоставляет глобальный метод fetch() (en-US), который позволяет легко и логично получать ресурсы по сети асинхронно.
Подобная функциональность ранее достигалась с помощью XMLHttpRequest . Fetch представляет собой лучшую альтернативу, которая может быть легко использована другими технологиями, такими как Service Workers (en-US). Fetch также обеспечивает единое логическое место для определения других связанных с HTTP понятий, такие как CORS и расширения для HTTP.
Обратите внимание, fetch спецификация отличается от jQuery.ajax() в основном в двух пунктах:
- Promise возвращаемый вызовом fetch() не перейдёт в состояние «отклонено» из-за ответа HTTP, который считается ошибкой, даже если ответ HTTP 404 или 500. Вместо этого, он будет выполнен нормально (с значением false в статусе ok ) и будет отклонён только при сбое сети или если что-то помешало запросу выполниться.
- По умолчанию, fetch не будет отправлять или получать cookie файлы с сервера, в результате чего запросы будут осуществляться без проверки подлинности, что приведёт к неаутентифицированным запросам, если сайт полагается на проверку пользовательской сессии (для отправки cookie файлов в аргументе init options должно быть задано значение свойства credentials отличное от значения по умолчанию omit ).
Примечание: 25 августа 2017 г. в спецификации изменилось значение по умолчанию свойства credentials на same-origin . Firefox применяет это изменение с версии 61.0b13.
Базовый запрос на получение данных действительно прост в настройке. Взгляните на следующий код:
Здесь мы забираем JSON файл по сети и выводим его содержимое в консоль. Самый простой способ использования fetch() заключается в вызове этой функции с одним аргументом — строкой, содержащей путь к ресурсу, который вы хотите получить — которая возвращает promise, содержащее ответ (объект Response ).
Конечно, это просто HTTP-ответ, а не фактический JSON. Чтобы извлечь содержимое тела JSON из ответа, мы используем json() (en-US) метод (определён миксином Body , который реализован в объектах Request и Response .)
Примечание: Миксин Body имеет подобные методы для извлечения других типов контента; см. раздел Тело.
Fetch-запросы контролируются посредством директивы connect-src (Content Security Policy (en-US) ), а не директивой извлекаемых ресурсов.
Установка параметров запроса
Метод fetch() может принимать второй параметр — объект init , который позволяет вам контролировать различные настройки:
С подробным описанием функции и полным списком параметров вы можете ознакомиться на странице fetch() (en-US).
Отправка запроса с учётными данными
Чтобы браузеры могли отправлять запрос с учётными данными (даже для cross-origin запросов), добавьте credentials: ‘include’ в объект init , передаваемый вами в метод fetch() :
Если вы хотите отправлять запрос с учётными данными только если URL принадлежит одному источнику (origin) что и вызывающий его скрипт, добавьте credentials: ‘same-origin’.
Напротив, чтобы быть уверенным, что учётные данные не передаются с запросом, используйте credentials: ‘omit’:
Отправка данных в формате JSON
При помощи fetch() (en-US) можно отправлять POST-запросы в формате JSON.
Загрузка файла на сервер
На сервер можно загрузить файл, используя комбинацию HTML-элемента <input type=»file» /> , FormData() и fetch() .
Загрузка нескольких файлов на сервер
На сервер можно загрузить несколько файлов, используя комбинацию HTML-элемента <input type=»file» multiple /> , FormData() и fetch() .
Обработка текстового файла построчно
Фрагменты данных, получаемые из ответа, не разбиваются на строки автоматически (по крайней мере с достаточной точностью) и представляют собой не строки, а объекты Uint8Array . Если вы хотите загрузить текстовый файл и обрабатывать его по мере загрузки построчно, то на вас самих ложится груз ответственности за обработку всех упомянутых моментов. Как пример, далее представлен один из способов подобной обработки с помощью создания построчного итератора (для простоты приняты следующие допущения: текст приходит в кодировке UTF-8 и ошибки получения не обрабатываются).
Проверка успешности запроса
В методе fetch() (en-US) promise будет отклонён (reject) с TypeError , когда случится ошибка сети или не будет сконфигурирован CORS на стороне запрашиваемого сервера, хотя обычно это означает проблемы доступа или аналогичные — для примера, 404 не является сетевой ошибкой. Для достоверной проверки успешности fetch() будет включать проверку того, что promise успешен (resolved), затем проверку того, что значение свойства Response.ok (en-US) является true. Код будет выглядеть примерно так:
Составление своего объекта запроса
Вместо передачи пути ресурса, который вы хотите запросить вызовом fetch(), вы можете создать объект запроса, используя конструктор Request() (en-US), и передать его в fetch() аргументом:
Конструктор Request() принимает точно такие же параметры, как и метод fetch(). Вы даже можете передать существующий объект запроса для создания его копии:
Довольно удобно, когда тела запроса и ответа используются единожды (прим.пер.: «are one use only»). Создание копии как показано позволяет вам использовать запрос/ответ повторно, при изменении опций init, при желании. Копия должна быть сделана до прочтения тела, а чтение тела в копии также пометит его прочитанным в исходном запросе.
Примечание: Также есть метод clone() (en-US), создающий копии. Оба метода создания копии прекратят работу с ошибкой если тело оригинального запроса или ответа уже было прочитано, но чтение тела клонированного ответа или запроса не приведёт к маркировке оригинального.
Заголовки
Интерфейс Headers (en-US) позволяет вам создать ваш собственный объект заголовков через конструктор Headers() (en-US). Объект заголовков — простая мультикарта имён-значений:
То же может быть достигнуто путём передачи массива массивов или литерального объекта конструктору:
Содержимое может быть запрошено и извлечено:
Некоторые из этих операций могут быть использованы только в ServiceWorkers (en-US), но они предоставляют более удобный API для манипуляции заголовками.
Все методы Headers выбрасывают TypeError, если имя используемого заголовка не является валидным именем HTTP Header. Операции мутации выбросят TypeError если есть защита от мутации (смотрите ниже) (прим.пер.: «if there is an immutable guard»). В противном случае они прерываются молча. Например:
Хорошим вариантом использования заголовков является проверка корректности типа контента перед его обработкой. Например:
Защита
С тех пор как заголовки могут передаваться в запросе, приниматься в ответе и имеют различные ограничения в отношении того, какая информация может и должна быть изменена, заголовки имеют свойство guard. Это не распространяется на Web, но влияет на то, какие операции мутации доступны для объекта заголовков.
none: по умолчанию.request: защита объекта заголовков, полученного по запросу ( Request.headers (en-US)).request-no-cors: защита объекта заголовков, полученного по запросу созданного с Request.mode no-cors.response: защита Headers полученных от ответа ( Response.headers (en-US)).immutable: в основном, используется в ServiceWorkers; делает объект заголовков read-only.
Примечание: вы не можете добавить или установить request защищаемые Headers’ заголовок Content-Length. Аналогично, вставка Set-Cookie в заголовок ответа недопустимо: ServiceWorkers не допускают установки cookies через синтезированные ответы.
Как вы видели выше, экземпляр Response будет возвращён когда fetch() промис будет исполнен.
Свойства объекта-ответа которые чаще всего используются:
Response.status (en-US) — Целочисленное (по умолчанию 200) содержит код статуса ответа. Response.statusText (en-US) — Строка (по умолчанию»OK»), которая соответствует HTTP коду статуса. Response.ok (en-US) — как сказано ранее, это короткое свойство для упрощения проверки на то что статус ответа находится где-то между 200-299 включительно. Это свойство типа Boolean (en-US).
Они так же могут быть созданы с помощью JavaScript, но реальная польза от этого есть только при использовании сервис-воркеров (en-US), когда вы предоставляете собственный ответ на запрос с помощью метода respondWith() (en-US):
Конструктор Response() принимает два необязательных аргумента — тело для ответа и объект init (аналогичный тому, который принимает Request() (en-US))
Примечание: Метод error() (en-US) просто возвращает ответ об ошибке. Аналогично, redirect() (en-US) возвращает ответ, приводящий к перенаправлению на указанный URL. Они также относятся только к Service Workers.
Запрос и ответ могут содержать данные тела. Тело является экземпляром любого из следующих типов:
Body примесь определяет следующие методы для извлечения тела (реализованы как для Request так и для Response ). Все они возвращают promise, который в конечном итоге исполняется и выводит содержимое.
Это делает использование нетекстовых данных более лёгким, чем при XMR.
В запросе можно установить параметры для отправки тела запроса:
Параметры request и response (and by extension the fetch() function), по возможности возвращают корректные типы данных. Параметр request также автоматически установит Content-Type в заголовок, если он не был установлен из словаря.
Функция обнаружения
Поддержка Fetch API может быть обнаружена путём проверки наличия Headers (en-US), Request , Response или fetch() (en-US) в области видимости Window или Worker . Для примера:
Полифил
BCD tables only load in the browser
Для того, чтобы использовать Fetch в неподдерживаемых браузерах, существует Fetch Polyfill , который воссоздаёт функциональность для не поддерживающих браузеров.
СпецификацииSpecification Status CommentFetch Живой стандарт Initial definitionСовместимость браузера
Смотрите такжеServiceWorker APIHTTP access control (CORS)HTTPFetch polyfillFetch examples on Github`
JavaScript Fetch API
Самым большим отличием Fetch от XMLHttpRequest является то, что первый использует промисы, которые значительно упрощают работу с запросами и ответами. Код на Fetch получается более простым и чистым.
Начиная с ES7, вы можете использовать async-await и полностью избавиться от обещаний.
Fetch API предоставляет глобальный метод fetch():
Отправка запроса и чтение ответа
Если не указывать метод, то Fetch API по умолчанию делает GET-запрос.
Метод fetch() возвращает promise. Для обработки результата можно использовать методы then() и catch() :
При успешном выполнении запроса, мы получим объект Response . У Response есть ряд полезных свойств для проверки состояния ответа:
- status – код статуса;
- statusText – текст статуса;
- ok – true , когда код статуса от 200 до 299;
- redirected – true , если при вызове запрошенного URL-адреса произошёл редирект.
Проверить, выполнен ли запрос успешно, можно с помощью свойства ok :
Промис ( request ) завершается успешно даже когда запрошенный URL не существует (код ответа 404) или он вызывает ошибку 500. Просто будут другие значения свойств: status , ok и т.д.
В метод catch мы попадём только в том случае, когда fetch() вообще не может выполнить запрос, т.е. нет такого сайта или произошла потеря сетевого соединения.
Для получения тела ответа, у объекта Response имеются следующие методы:
- text() – как текст;
- json() – в формате JSON;
- formData() – как объект FormData;
- blob() – в формате Blob;
- arrayBuffer() – как ArrayBuffer.
Все эти методы возвращают promise, который в конечном итоге выполняется и выводит содержимое.
Например, прочитаем ответ как строку и выведем её в элемент с id=»result» :
Вместо обещаний можно использовать async-await:
Пример использования fetch() для получения JSON
Напишем пример, в котором будем получать информацию о пользователях в формате JSON. Для того, чтобы запросить данные об конкретном пользователе будем брать значение из поля, а затем добавлять его в URL посредством GET-параметра id .

Содержимое файла «05.php», который возвращает данные о пользователях в формате JSON: