Получение данных с сервера - Изучение веб-разработки

Получение данных с сервера
==========================

* Назад
* Обзор: Client-side web APIs
* Далее

Другой очень распространённой задачей в современных веб-сайтах и приложениях является получение отдельных элементов данных с сервера для обновления разделов веб-страницы без необходимости загрузки всей новой страницы. Эта, казалось бы, небольшая деталь оказала огромное влияние на производительность и поведение сайтов, поэтому в этой статье мы объясним концепцию и рассмотрим технологии, которые делают это возможным, например XMLHttpRequest и API Fetch.

| Необходимые условия: | Основы JavaScript (см. первые шаги, структурные элементы, объекты JavaScript), основы клиентских API |
| --- | --- |
| Задача: | Узнать, как извлекать данные с сервера и использовать их для обновления содержимого веб-страницы. |

В чем проблема?
---------------

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

![A basic representation of a web site architecture](/ru/docs/Learn/JavaScript/Client-side_web_APIs/Fetching_data/web-site-architechture@2x.png)

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

### Появление Ajax

Это привело к созданию технологий, позволяющих веб-страницам запрашивать небольшие фрагменты данных (например, HTML, XML, JSON или обычный текст) и отображать их только при необходимости, помогая решать проблему, описанную выше.

Это достигается с помощью таких API, как `XMLHttpRequest` или - более новой - Fetch API. Эти технологии позволяют веб-страницам напрямую обрабатывать запросы HTTP для определённых ресурсов, доступных на сервере, и форматировать результирующие данные по мере необходимости перед их отображением.

**Примечание:** Вначале эта общая техника была известна как Асинхронный JavaScript и XML (Ajax), поскольку она, как правило, использовала `XMLHttpRequest` для запроса данных XML. В наши дни это обычно не так (вы, скорее всего, будете использовать `XMLHttpRequest` или Fetch для запроса JSON), но результат все тот же, и термин «Ajax» по-прежнему часто используется для описания этой техники.

![A simple modern architecture for web sites](/ru/docs/Learn/JavaScript/Client-side_web_APIs/Fetching_data/moderne-web-site-architechture@2x.png)

Модель Ajax предполагает использование веб-API в качестве прокси для более разумного запроса данных, а не просто для того, чтобы браузер перезагружал всю страницу. Давайте подумаем о значении этого:

1. Перейдите на один из ваших любимых сайтов, богатых информацией, таких как Amazon, YouTube, CNN и т.д., и загрузите его.
2. Теперь найдите что-нибудь, например, новый продукт. Основной контент изменится, но большая часть информации, подобной заголовку, нижнему колонтитулу, навигационному меню и т. д., останется неизменной.

Это действительно хорошо, потому что:

* Обновления страницы намного быстрее, и вам не нужно ждать перезагрузки страницы, а это означает, что сайт работает быстрее и воспринимается более отзывчивым.
* Меньше данных загружается при каждом обновлении, что означает меньшее потребление пропускной способности. Это не может быть такой большой проблемой на рабочем столе в широкополосном подключении, но это серьёзная проблема на мобильных устройствах и в развивающихся странах, которые не имеют повсеместного быстрого интернет-сервиса.

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

![A basic web app data flow architecture](/ru/docs/Learn/JavaScript/Client-side_web_APIs/Fetching_data/web-app-architecture@2x.png)

Основной запрос Ajax
--------------------

Давайте посмотрим, как обрабатывается такой запрос, используя как `XMLHttpRequest`, так и Fetch. В этих примерах мы будем запрашивать данные из нескольких текстовых файлов и использовать их для заполнения области содержимого.

Этот набор файлов будет действовать как наша поддельная база данных; в реальном приложении мы с большей вероятностью будем использовать серверный язык, такой как PHP, Python или Node, чтобы запрашивать наши данные из базы данных. Здесь, однако, мы хотим сохранить его простым и сосредоточиться на стороне клиента.

### XMLHttpRequest

`XMLHttpRequest` (который часто сокращается до XHR) является довольно старой технологией сейчас - он был изобретён Microsoft в конце 1990-х годов и уже довольно долго стандартизирован в браузерах.

1. Чтобы начать этот пример, создайте локальную копию ajax-start.html и четырёх текстовых файлов - verse1.txt, verse2.txt, verse3.txt и verse4.txt - в новом каталоге на вашем компьютере. В этом примере мы загрузим другое стихотворение (который вы вполне можете распознать) через XHR, когда он будет выбран в выпадающем меню.
2. Внутри элемента `<script>` добавьте следующий код. В нем хранится ссылка на элементы `<select>` и `<pre>` в переменных и определяется `onchange` обработчика событий, так что, когда значение select изменяется, его значение передаётся вызываемой функции `updateDisplay()` в качестве параметра.

```js
var verseChoose = document.querySelector("select");
var poemDisplay = document.querySelector("pre");

verseChoose.onchange = function () {
  var verse = verseChoose.value;
  updateDisplay(verse);
};
```

3. Давайте определим нашу функцию `updateDisplay()`. Прежде всего, поставьте следующее ниже своего предыдущего блока кода - это пустая оболочка функции:

```js
function updateDisplay(verse) {}
```

4. Мы начнём нашу функцию с создания относительного URL-адреса, указывающего на текстовый файл, который мы хотим загрузить и который понадобится нам позже. Значение элемента `<select>` в любой момент совпадает с текстом внутри выбранного `<option>` (если вы не укажете другое значение в атрибуте value) - например, «Verse 1». Соответствующий текстовый файл стиха является «verse1.txt» и находится в том же каталоге, что и файл HTML, поэтому будет использоваться только имя файла.
 Тем не менее, веб-серверы, как правило, чувствительны к регистру, и имя файла не имеет символа "пробела". Чтобы преобразовать «Verse 1» в «verse1.txt», нам нужно преобразовать V в нижний регистр, удалить пробел и добавить .txt в конец. Это можно сделать с помощью `replace ()`, `toLowerCase ()` и простой конкатенации строк. Добавьте следующие строки внутри функции `updateDisplay()`:

```js
verse = verse.replace(" ", "");
verse = verse.toLowerCase();
var url = verse + ".txt";
```

5. Чтобы начать создание запроса XHR, вам нужно создать новый объект запроса, используя конструктор `XMLHttpRequest()`. Вы можете назвать этот объект так, как вам нравится, но мы будем называть его `request` (запросом), чтобы все было просто. Добавьте следующие ниже строки:

```js
var request = new XMLHttpRequest();
```

6. Затем вам нужно использовать метод `open()`, чтобы указать, какой HTTP request method использовать для запроса ресурса из сети и какой его URL-адрес. Мы просто используем метод `GET` здесь и задаём URL как нашу переменную `url`. Добавьте это ниже вашей предыдущей строки:

```js
request.open("GET", url);
```

7. Затем мы зададим тип ожидаемого ответа, который определяется как свойство `responseType` - как `text`. Здесь это не является абсолютно необходимым - XHR возвращает текст по умолчанию - но это хорошая идея, чтобы привыкнуть к настройке этого, если вы хотите получить другие типы данных в будущем. Добавьте следующее:

```js
request.responseType = "text";
```

8. Получение ресурса из сети - это asynchronous операция, означающая, что вам нужно дождаться завершения этой операции (например, ресурс возвращается из сети), прежде чем вы сможете сделать что-либо с этим ответом, иначе будет выброшена ошибка. XHR позволяет вам обрабатывать это, используя обработчик события `onload` - он запускается при возникновении события `load (en-US)` (когда ответ вернулся). Когда это произойдёт, данные ответа будут доступны в свойстве `response` (ответ) объекта запроса XHR.
 Добавьте следующее ниже вашего последнего дополнения. Вы увидите, что внутри обработчика события `onload` мы устанавливаем textContent `poemDisplay` (элемент `<pre>`) в значение `request. response` .

```js
request.onload = function () {
  poemDisplay.textContent = request.response;
};
```

9. Вышеприведённая конфигурация запроса XHR фактически не будет выполняться до тех пор, пока мы не вызовем метод `send()`. Добавьте следующее ниже вашего предыдущего дополнения для вызова функции:

```js
request.send();
```

10. Одна из проблем с примером заключается в том, что он не покажет ни одного стихотворения, когда он впервые загружается. Чтобы исправить это, добавьте следующие две строки внизу вашего кода (чуть выше закрывающего тега `</script>`), чтобы загрузить стих 1 по умолчанию и убедитесь, что элемент `<select>` всегда показывает правильное значение:

```js
updateDisplay("Verse 1");
verseChoose.value = "Verse 1";
```

### Обслуживание вашего примера с сервера

Некоторые браузеры (включая Chrome) не будут запускать запросы XHR, если вы просто запускаете пример из локального файла. Это связано с ограничениями безопасности (для получения дополнительной информации о безопасности в Интернете, ознакомьтесь с Website security).

Чтобы обойти это, нам нужно протестировать пример, запустив его через локальный веб-сервер. Чтобы узнать, как это сделать, прочитайте Как настроить локальный тестовый сервер?

### Fetch

API-интерфейс Fetch - это, в основном, современная замена XHR - недавно он был представлен в браузерах для упрощения асинхронных HTTP-запросов в JavaScript, как для разработчиков, так и для других API, которые строятся поверх Fetch.

Давайте преобразуем последний пример, чтобы использовать Fetch!

1. Сделайте копию своего предыдущего готового каталога примеров. (Если вы не работали над предыдущим упражнением, создайте новый каталог и внутри него создайте копии xhr-basic.html и четырёх текстовых файлов — verse1.txt, verse2.txt, verse3.txt и verse4.txt.)
2. Внутри функции `updateDisplay()` найдите код XHR:

```js
var request = new XMLHttpRequest();
request.open("GET", url);
request.responseType = "text";

request.onload = function () {
  poemDisplay.textContent = request.response;
};

request.send();
```

3. Замените весь XHR-код следующим:

```js
fetch(url).then(function (response) {
  response.text().then(function (text) {
    poemDisplay.textContent = text;
  });
});
```

4. Загрузите пример в свой браузер (запустите его через веб-сервер), и он должен работать так же, как и версия XHR, при условии, что вы используете современный браузер.

#### Итак, что происходит в коде Fetch?

Прежде всего, мы вызываем метод `fetch()`, передавая ему URL-адрес ресурса, который мы хотим получить. Это современный эквивалент `request.open()` в XHR, плюс вам не нужен эквивалент `.send()`.

После этого вы можете увидеть метод `.then()`, прикреплённый в конец `fetch()` - этот метод является частью `Promises` - современная функция JavaScript для выполнения асинхронных операций. `fetch()` возвращает промис, который разрешает ответ, отправленный обратно с сервера, - мы используем `.then()` для запуска некоторого последующего кода после того, как промис будет разрешено, что является функцией, которую мы определили внутри неё. Это эквивалент обработчика события `onload` в XHR-версии.

Эта функция автоматически передаёт ответ от сервера в качестве параметра, когда обещает `fetch()`. Внутри функции мы берём ответ и запускаем его метод `text()` (en-US), который в основном возвращает ответ как необработанный текст. Это эквивалент `request.responseType = 'text'` в версии XHR.

Вы увидите, что `text()` также возвращает промис, поэтому мы привязываем к нему другой `.then()`, внутри которого мы определяем функцию для получения необработанного текста, который выполняет `text()`.

Внутри функции внутреннего промиса мы делаем то же самое, что и в версии XHR, - устанавливаем текстовое содержимое `<pre>` в текстовое значение.

### Помимо промисов

Промисы немного запутывают первый раз, когда вы их встречаете, но не беспокойтесь об этом слишком долго. Через некоторое время вы привыкнете к ним, особенно, когда вы узнаете больше о современных JavaScript-API. Большинство из них в большей степени основаны на промисах.

Давайте посмотрим на структуру промисов сверху, чтобы увидеть, можем ли мы ещё немного понять это:

```js
fetch(url).then(function (response) {
  response.text().then(function (text) {
    poemDisplay.textContent = text;
  });
});
```

В первой строке говорится: «Получить ресурс, расположенный по адресу url» `(fetch(url)`) и «затем запустить указанную функцию, когда промис будет разрешено» (`.then(function() { ... })`). «Resolve» означает «завершить выполнение указанной операции в какой-то момент в будущем». Указанная операция в этом случае заключается в извлечении ресурса с указанного URL (с использованием HTTP-запроса) и возврата ответа для нас, чтобы что-то сделать.

Фактически, функция, переданная в `then()`, представляет собой кусок кода, который не запускается немедленно - вместо этого он будет работать в какой-то момент в будущем, когда ответ будет возвращён. Обратите внимание, что вы также можете сохранить своё промис в переменной и цепочку `.then()` вместо этого. Ниже код будет делать то же самое:

```js
var myFetch = fetch(url);

myFetch.then(function (response) {
  response.text().then(function (text) {
    poemDisplay.textContent = text;
  });
});
```

Поскольку метод `fetch()` возвращает промис, который разрешает HTTP-ответ, любая функция, которую вы определяете внутри `.then()`, прикованная к концу, будет автоматически передаваться как параметр. Вы можете вызвать параметр, который вам нравится - приведённый ниже пример будет работать:

```js
fetch(url).then(function (dogBiscuits) {
  dogBiscuits.text().then(function (text) {
    poemDisplay.textContent = text;
  });
});
```

Но имеет смысл называть параметр тем, что описывает его содержимое!

Теперь давайте сосредоточимся только на функции:

```js
function(response) {
  response.text().then(function(text) {
    poemDisplay.textContent = text;
  });
}
```

Объект ответа имеет метод `text()` (en-US), который берёт необработанные данные, содержащиеся в теле ответа, и превращает его в обычный текст, который является форматом, который мы хотим в нем А также возвращает промис (который разрешает полученную текстовую строку), поэтому здесь мы используем другой `.then()`, внутри которого мы определяем другую функцию, которая диктует что мы хотим сделать с этой текстовой строкой. Мы просто устанавливаем свойство `textContent` элемента `<pre>` нашего стихотворения равным текстовой строке, так что это получается довольно просто.

Также стоит отметить, что вы можете напрямую связывать несколько блоков промисов (`.then()`, но есть и другие типы) на конце друг друга, передавая результат каждого блока следующему блоку по мере продвижения по цепочке , Это делает промисы очень мощными.

Следующий блок делает то же самое, что и наш оригинальный пример, но написан в другом стиле:

```js
fetch(url)
  .then(function (response) {
    return response.text();
  })
  .then(function (text) {
    poemDisplay.textContent = text;
  });
```

Многие разработчики любят этот стиль больше, поскольку он более плоский и, возможно, легче читать для более длинных цепочек промисов - каждое последующее промис приходит после предыдущего, а не внутри предыдущего (что может стать громоздким). Единственное отличие состоит в том, что мы должны были включить оператор `return` перед `response.text()`, чтобы заставить его передать результат в следующую ссылку в цепочке.

### Какой механизм следует использовать?

Это действительно зависит от того, над каким проектом вы работаете. XHR существует уже давно и имеет отличную кросс-браузерную поддержку. Fetch and Promises, с другой стороны, являются более поздним дополнением к веб-платформе, хотя они хорошо поддерживаются в браузере, за исключением Internet Explorer и Safari (которые на момент написания Fetch были доступны в своём предварительный просмотр технологии).

Если вам необходимо поддерживать старые браузеры, тогда может быть предпочтительным решение XHR. Если, однако, вы работаете над более прогрессивным проектом и не так обеспокоены старыми браузерами, то Fetch может быть хорошим выбором.

Вам действительно нужно учиться - Fetch станет более популярным, так как Internet Explorer отказывается от использования (IE больше не разрабатывается, в пользу нового браузера Microsoft Edge), но вам может понадобиться XHR ещё некоторое время.

Более сложный пример
--------------------

Чтобы завершить статью, мы рассмотрим несколько более сложный пример, который показывает более интересные применения Fetch. Мы создали образец сайта под названием The Can Store - это вымышленный супермаркет, который продаёт только консервы. Вы можете найти этот пример в прямом эфире на GitHub и посмотреть исходный код.

![A fake ecommerce site showing search options in the left hand column, and product search results in the right hand column.](/ru/docs/Learn/JavaScript/Client-side_web_APIs/Fetching_data/can-store.png)

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

Существует довольно много сложного кода, который включает фильтрацию продуктов по категориям и поисковым запросам, манипулирование строками, чтобы данные отображались правильно в пользовательском интерфейсе и т.д. Мы не будем обсуждать все это в статье, но вы можете найти обширные комментарии в коде (см. can-script.js).

Однако мы объясним код Fetch.

Первый блок, который использует Fetch, можно найти в начале JavaScript:

```js
fetch("products.json").then(function (response) {
  if (response.ok) {
    response.json().then(function (json) {
      products = json;
      initialize();
    });
  } else {
    console.log(
      "Network request for products.json failed with response " +
        response.status +
        ": " +
        response.statusText,
    );
  }
});
```

Это похоже на то, что мы видели раньше, за исключением того, что второй промис находится в условном выражении. В этом случае мы проверяем, был ли возвращённый ответ успешным - свойство `response.ok` (en-US) содержит логическое значение, которое `true`, если ответ был в порядке (например, 200 meaning "OK") или `false`, если он не увенчался успехом.

Если ответ был успешным, мы выполняем второй промис - на этот раз мы используем `json()` (en-US), а не `text()` (en-US), так как мы хотим вернуть наш ответ как структурированные данные JSON, а не обычный текст.

Если ответ не увенчался успехом, мы выводим сообщение об ошибке в консоль, в котором сообщается о сбое сетевого запроса, который сообщает о статусе сети и описательном сообщении ответа (содержащемся в `response.status` (en-US) и `response.statusText` (en-US), соответственно). Конечно, полный веб-сайт будет обрабатывать эту ошибку более грациозно, отображая сообщение на экране пользователя и, возможно, предлагая варианты для исправления ситуации.

Вы можете проверить сам случай отказа:

1. Создание локальной копии файлов примеров (загрузка и распаковка the can-store ZIP file)
2. Запустите код через веб-сервер (как описано выше, в Serving your example from a server)
3. Измените путь к извлечённому файлу, например, «product.json» (т.е. убедитесь, что он написан неправильно)
4. Теперь загрузите индексный файл в свой браузер (например, через `localhost:8000`) и посмотрите в консоли разработчика браузера. Вы увидите сообщение в строке «Запрос сети для продуктов.json не удалось с ответом 404: Файл не найден»

Второй блок Fetch можно найти внутри функции `fetchBlob()`:

```js
fetch(url).then(function (response) {
  if (response.ok) {
    response.blob().then(function (blob) {
      objectURL = URL.createObjectURL(blob);
      showProduct(objectURL, product);
    });
  } else {
    console.log(
      'Network request for "' +
        product.name +
        '" image failed with response ' +
        response.status +
        ": " +
        response.statusText,
    );
  }
});
```

Это работает во многом так же, как и предыдущий, за исключением того, что вместо использования `json()` (en-US) мы используем `blob()` (en-US) - в этом случае мы хотим вернуть наш ответ в виде файла изображения, а формат данных, который мы используем для этого - Blob - этот термин является аббревиатурой от« Binary Large Object »и может в основном использоваться для представляют собой большие файловые объекты, такие как изображения или видеофайлы.

После того как мы успешно получили наш blob, мы создаём URL-адрес объекта, используя `createObjectURL()`. Это возвращает временный внутренний URL-адрес, указывающий на объект, указанный в браузере. Они не очень читаемы, но вы можете видеть, как выглядит, открывая приложение Can Store, Ctrl-/щёлкнуть правой кнопкой мыши по изображению и выбрать опцию «Просмотр изображения» (которая может немного отличаться в зависимости от того, какой браузер вы ). URL-адрес объекта будет отображаться внутри адресной строки и должен выглядеть примерно так:
```

blob:http://localhost:7800/9b75250e-5279-e249-884f-d03eb1fd84f4
```

### Вызов: XHR версия the Can Store

Мы хотели бы, чтобы вы решили преобразовать версию приложения Fetch для использования XHR в качестве полезной части практики. Возьмите копию ZIP файла и попробуйте изменить JavaScript, если это необходимо.

Некоторые полезные советы:

* Вы можете найти полезный справочный материал `XMLHttpRequest`.
* Вам в основном нужно использовать тот же шаблон, что и раньше, в примере XHR-basic.html.
* Однако вам нужно будет добавить обработку ошибок, которые мы показали вам в версии Fetch Can Store:
    + Ответ найден в `request.response` после того, как событие `load` запущено, а не в промисе `then()`.
    + О наилучшем эквиваленте Fetch's `response.ok` в XHR следует проверить, является ли `request.status` равным 200 или если `request.readyState` равно 4.
    + Свойства для получения статуса и сообщения состояния одинаковы, но они находятся на объекте `request` (XHR), а не в объекте `response`.

**Примечание:** Если у вас есть проблемы с этим, не стесняйтесь сравнить свой код с готовой версией на GitHub (см. исходник здесь, а также см. это в действии).

Резюме
------

Это завершает нашу статью по извлечению данных с сервера. К этому моменту вы должны иметь представление о том, как начать работать как с XHR, так и с Fetch.

Смотрите также
--------------

Однако в этой статье обсуждается много разных тем, которые только поцарапали поверхность. Для получения более подробной информации по этим темам, попробуйте следующие статьи:

* Введение в Ajax (en-US)
* Применение Fetch
* Promises
* Работа с JSON данными
* Обзор HTTP
* Программирование веб-сайта на стороне сервера
* Назад
* Обзор: Client-side web APIs
* Далее


Введение в web APIs - Изучение веб-разработки

Введение в web APIs
===================

* Обзор: Client-side web APIs
* Далее

Начнём с рассмотрения того что представляют собой API на высоком уровне и выясним, как они работают, как их использовать в своих программах и как они структурированы. Также рассмотрим основные виды API и их применение.

| Необходимые знания: | Базовая компьютерная грамотность, понимание основ HTML и CSS, основы JavaScript (см. первые шаги, building blocks, объекты JavaScript). |
| --- | --- |
| Цель: | Познакомиться с API, выяснить что они могут делать и как их использовать. |

Что такое API?
--------------

Интерфейс прикладного программирования (Application Programming Interfaces, APIs) — это готовые конструкции языка программирования, позволяющие разработчику строить сложную функциональность с меньшими усилиями. Они "скрывают" более сложный код от программиста, обеспечивая простоту использования.

Для лучшего понимания рассмотрим аналогию с домашними электросетями. Когда вы хотите использовать какой-то электроприбор, вы просто подключаете его к розетке, и всё работает. Вы не пытаетесь подключить провода напрямую к источнику тока — делать это бесполезно и, если вы не электрик, сложно и опасно.

![Подключение к разетке](/ru/docs/Learn/JavaScript/Client-side_web_APIs/Introduction/plug-socket.png)

*Image source: Overloaded plug socket by The Clear Communication People, on Flickr.*

Точно также, если мы хотим, например, программировать 3D графику, гораздо легче сделать это с использованием API, написанных на языках высокого уровня, таких как JavaScript или Python.

**Примечание:** Смотрите также API в словаре.

### API клиентской части JavaScript

Для JavaScript на стороне клиента, в частности, существует множество API. Они не являются частью языка, а построены с помощью встроенных функций JavaScript для того, чтобы увеличить ваши возможности при написании кода. Их можно разделить на две категории:

* **API браузера** встроены в веб-браузер и способны использовать данные браузера и компьютерной среды для осуществления более сложных действий с этими данными. К примеру, Web Audio API предоставляет конструкции JavaScript для работы с аудио в браузере, например,можно взять звуковую дорожку, изменить её громкость или применить к ней эффекты. На самом деле, в браузере выполняется сложный низкоуровневый код (например, на C++ или Rust) для обработки звука, но, как было сказано выше, эти детали скрыты благодаря API.
* **Сторонние API** не встроены в браузер по умолчанию. Такие API и информацию о них обычно необходимо искать в интернете. Например, Twitter API позволяет размещать последние твиты (tweets) на вашем веб-сайте. В данном API определён набор конструкций, осуществляющих запросы к сервисам Twitter и возвращающих определённые данные.

![Схема, описывающая работу API браузера](/ru/docs/Learn/JavaScript/Client-side_web_APIs/Introduction/browser.png)

### Взаимодействие JavaScript, API и других средств JavaScript

Итак, выше мы поговорили о том, что такое JavaScript API клиентской части и как они связаны с языком JavaScript. Давайте теперь тезисно запишем основные понятия и определим назначение других инструментов JavaScript:

* JavaScript — Язык программирования сценариев высокого уровня, встроенный в браузер, позволяющий создавать функциональность веб-страниц/приложений. Отметим, что JavaScript также доступен на других программных платформах, таких как Node.js. Но пока не будем останавливаться на этом.
* API браузера (Browser APIs) — конструкции, встроенные в браузер, построенные на основе языка JavaScript, предназначенные для облегчения разработки функциональности.
* Сторонние API (Third party APIs) — конструкции, встроенные в сторонние платформы (такие как Twitter, Facebook) позволяющие вам использовать часть функциональности этих платформ в своих собственных веб-страницах/приложениях (например, показывать последние Твиты на вашей странице).
* Библиотеки JavaScript — Обычно один или несколько файлов, содержащих пользовательские (custom) функции. Такие файлы можно прикрепить к веб-странице, чтобы ускорить или предоставить инструменты для написания общего функциональности. Примеры: jQuery, Mootools и React.
* JavaScript фреймворки (frameworks) — cледующий шаг в развитии разработки после библиотек. Фреймворки JavaScript (такие как Angular и Ember) стремятся к тому, чтобы быть набором HTML, CSS, JavaScript и других технологий, после установки которого можно "писать" веб-приложение с нуля. Главное различие между фреймворками и библиотеками — "обратное направление управления" ( "Inversion of Control" ). Вызов метода из библиотеки происходит по требованию разработчика. При использовании фреймворка — наоборот, фреймворк производит вызов кода разработчика.
На что способны API?
--------------------

Широкое разнообразие API в современных браузерах позволяет наделить ваше приложение большими возможностями. Достаточно посмотреть список на странице MDN APIs index page.

### Распространённые API браузера

В частности, к наиболее часто используемым категориям API (и которые мы рассмотрим далее в этом модуле) относятся :

* **API для работы с документами**, загруженными в браузер. Явный пример — DOM (Document Object Model) API, позволяющий работать с HTML и CSS — создавать, удалять и изменять HTML, динамически изменять вид страницы и т.д. Любое всплывающее окно на странице или появляющееся "на ходу" содержимое — всё это благодаря DOM. Узнайте больше об этой категории API на странице Работа с документами.
* **API, принимающие данные от сервера**, часто используются, чтобы обновить небольшие части веб-страницы. Эта, казалось бы, малая деталь оказывает огромное влияние на производительность и поведение сайтов, так как нет необходимости перезагружать всю страницу целиком, если вам нужно просто обновить список товаров или новых доступных историй. Это также сделает приложение или сайт более отзывчивым и "живым". Список API, благодаря которым это возможно, включает: `XMLHttpRequest` и Fetch API. Вы также могли встретить термин **Ajax**, описывающий эту технологию. Узнать больше об этой категории API на странице Получение данных от сервера.
* **API для работы с графикой** широко поддерживаются браузерами, самые популярные: Canvas и WebGL, позволяющие программно изменять данные о пикселях, содержащиеся в элементе HTML `<canvas>` для создания 2D и 3D изображений. Например, вы можете нарисовать фигуры, скажем, прямоугольники или круги, импортировать изображение в canvas и применить к нему фильтры, такие как сепия или оттенки серого с помощью Canvas API, или создать сложное 3D-изображение с освещением и текстурами, используя WebGL. Такие API часто используют в сочетании с API создания анимационных циклов (таких как `window.requestAnimationFrame()`) и другими для создания постоянно меняющегося изображения на экране, как в мультфильмах или играх .
* **Аудио и Видео API** как `HTMLMediaElement`, Web Audio API, и WebRTC позволяют делать действительно интересные вещи с мультимедиа. Например, создать собственный пользовательский интерфейс (User Interface, UI) для проигрывания аудио/видео, вывод на экран субтитров, записывать видео с веб-камеры для обработки в canvas (см. выше) или для передачи на другой компьютер в видео-конференции, применять звуковые эффекты к аудио-файлам (такие как gain, distortion, panning и т.д.).
* **API устройств** — в основном, это API для обработки и считывания данных с современных устройств удобным для работы веб-приложений образом. Например, Geolocation API позволяет считать данные о местоположении устройства. Другие примеры включают уведомление пользователя о появившемся обновлении для веб-приложения с помощью системных уведомлений (см. Notifications API) или вибрации (см. Vibration API (en-US)).
* **API хранения данных на стороне пользователя** приобретают всё большее распространение в веб-браузерах — возможность хранить информацию на стороне клиента очень полезна, когда необходимо создать приложение, которое будет сохранять своё состояние между перезагрузками страницы, или даже работать, когда устройство не в сети. В данный момент доступно немало таких API. Например, простое хранилище данных в формате имя/значение (name/value) Web Storage API или хранилище данных в формате таблиц IndexedDB API.
### Распространённые сторонние API

Существует множество сторонних API; некоторые из наиболее популярных, которые вы рано или поздно будете использовать, включают:

* Twitter API для добавления такой функциональности, как показ последних твитов на сайте.
* Google Maps API для работы с картами на веб-странице (интересно, что Google Maps также использует этот API). Теперь это целый набор API, который может справляться с широким спектром задач, как свидетельствует Google Maps API Picker.
* Набор Facebook API позволяет использовать различные части платформы Facebook в вашем приложении, предоставляя, например, возможность входа в систему с логином Facebook, оплаты покупок в приложении, демонстрация целевой рекламы и т.д.
* YouTube API, предоставляющий возможность встраивать видео с YouTube на вашем сайте, производить поиск, создавать плейлисты и т.д.
* Twilio API — фреймворк для встраивания функциональности голосовой и видео связи в вашем приложении, отправки SMS/MMS из приложения и т.д.

**Примечание:** вы можете найти информацию о гораздо большем количестве сторонних API в Каталоге Web API.

Как работает API?
-----------------

Работа разных JavaScript API немного отличается, но, в основном, у них похожие функции и принцип работы.

### Они основаны на объектах

Взаимодействие с API в коде происходит через один или больше объектов JavaScript, которые служат контейнерами для информации, с которой работает API (содержится в свойствах объекта), и реализуют функциональность, которую предоставляет API (содержится в методах объекта).

**Примечание:** Если вам ещё не известно как работают объекты, советуем вернуться назад и изучить модуль Основы объектов JavaScript прежде чем продолжать.

Вернёмся к примеру с Web Audio API — это довольно сложный API, состоящий из ряда объектов. Наиболее очевидные из них

* `AudioContext`, представляющий аудиограф (en-US), который содержит ряд полезных свойств и методов для управления воспроизведением звука в браузере.
* `MediaElementAudioSourceNode` представляет элемент `<audio>`, включающий в себя звук, которым можно управлять внутри аудиоконтекста или воспроизвести.
* `AudioDestinationNode` (en-US) представляет место назначения звука, то есть устройство вашего компьютера, которые фактически выводит его. Обычно это динамики или наушники.

Так как же эти объекты взаимодействуют? Если вы посмотрите на наш пример simple web audio example (see it live also), вы увидите следующий код:

```html
<audio src="outfoxing.mp3"></audio>

<button class="paused">Play</button>
<br />
<input type="range" min="0" max="1" step="0.01" value="1" class="volume" />
```

Прежде всего, добавляем элемент `audio`, с помощью которого встраиваем MP3 на страницу. Мы не включаем какие-либо элементы управления по умолчанию. Далее добавляем элемент `<button>`, который будем использовать для воспроизведения и остановки музыки, и элемент `<input>` с типом range, он понадобится для регулировки громкости трека во время его воспроизведения.

Взглянем на JavaScript код этого примера.

Начнём с того, что создадим экземпляр конструктора `AudioContext`, с помощью которого будет манипулировать нашим треком.

```js
const AudioContext = window.AudioContext || window.webkitAudioContext;
const audioCtx = new AudioContext();
```

Далее создадим константы для наших `<audio>`, `<button>` и `<input>` элементов и воспользуемся методом `AudioContext.createMediaElementSource()` для создания `MediaElementAudioSourceNode`, представляющий источник аудио, с которым будет воспроизводиться элемент `audio`.

```js
const audioElement = document.querySelector("audio");
const playBtn = document.querySelector("button");
const volumeSlider = document.querySelector(".volume");

const audioSource = audioCtx.createMediaElementSource(audioElement);
```

Затем включим пару обработчиков событий для переключения между воспроизведением и паузой при нажании на кнопку и сброса воспроизведения в начало, когда песня закончится:

```js
playBtn.addEventListener("click", () => {
  // проверить, находится ли контекст в приостановленном состоянии
  if (audioCtx.state === "suspended") {
    audioCtx.resume();
  }

  // если трек остановлен, то начать его проигрывать
  if (playBtn.getAttribute("class") === "paused") {
    audioElement.play();
    playBtn.setAttribute("class", "playing");
    playBtn.textContent = "Pause";
    // если трек воспроизводится, приостановить проигрывание
  } else if (playBtn.getAttribute("class") === "playing") {
    audioElement.pause();
    playBtn.setAttribute("class", "paused");
    playBtn.textContent = "Play";
  }
});

// если трек закончился
audioElement.addEventListener("ended", () => {
  playBtn.setAttribute("class", "paused");
  playBtn.textContent = "Play";
});
```

**Примечание:** обратите внимание, что методы `play()` и `pause()`, используемые для воспроизведения и приостановки дорожки, не являются частью Web Audio API. Они являются частью `HTMLMediaElement` API, который отличается от Web Audio API, но тесно с ним связан.

Далее создадим объект `GainNode` (en-US) с помощью метода `AudioContext.createGain()` (en-US), который можно использовать для регулировки громкости звука, и еще один обработчик событий, с помощью которого будем менять значение усиления (громкости) в зависимости от значения `<input type="range">`.

```js
// volume
const gainNode = audioCtx.createGain();

volumeSlider.addEventListener("input", () => {
  gainNode.gain.value = volumeSlider.value;
});
```

Наконец, чтобы всё заработало, соединим различные узлы в аудиографе с помощью метода `AudioNode.connect()` (en-US), доступном на каждом узле этого типа:

```js
audioSource.connect(gainNode).connect(audioCtx.destination);
```

Звук начинается в источнике, который затем подключаетя к узлу усиления, чтобы можно было регулировать громкость звука. Затем узел усиления подключается к узлу назначения звука, чтобы его можно было воспроизвести на компьютере (по умолчанию свойство `AudioContext.destination` (en-US) равно значению  `AudioDestinationNode`, которое имеет доступ к аудио-оборудованию компьютера, например, к динамикам).

### У них узнаваемые точки входа

При использовании API убедитесь, что вы знаете где точка входа для API. В Web Audio API это довольно просто — экземпляр констуктора `AudioContext`, возвращает объект, внутри которого доступны все полезные методы и свойства для манипуляции звуком.

Найти точку входа Document Object Model (DOM) API ещё проще — при применении этого API используется объект `Document`, или экземпляр элемента HTML, с которым вы хотите каким-либо образом взаимодействовать, к примеру:

```js
const em = document.createElement("em"); // создаёт новый элемент em
const para = document.querySelector("p"); // ссылка на существующий элемент p
em.textContent = "Hello there!"; // присвоение текстового содержимого
para.appendChild(em); // встроить em внутрь para
```

Точки входа других API немного сложнее, часто подразумевается создание особого контекста, в котором будет написан код API. Например, объект контекста Canvas API создаётся получением ссылки на элемент `<canvas>`, на котором вы хотите рисовать. Затем необходимо вызвать метод `HTMLCanvasElement.getContext()`:

```js
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
```

Всё, что мы хотим сделать с canvas после этого, достигается вызовом свойств и методов объекта содержимого (content) (который является экземпляром `CanvasRenderingContext2D`), например:

```js
Ball.prototype.draw = function () {
  ctx.beginPath();
  ctx.fillStyle = this.color;
  ctx.arc(this.x, this.y, this.size, 0, 2 \* Math.PI);
  ctx.fill();
};
```

**Примечание:** вы можете увидеть этот код в действии в нашем bouncing balls demo (see it running live also).

### Они используют события для управления состоянием

Мы уже обсуждали события ранее в этом курсе, в нашей статье Введение в события — в ней детально описываются события на стороне клиента и их применение. Если вы ещё не знакомы с тем, как работают события клиентской части, рекомендуем прочитать эту статью прежде, чем продолжить.

В некоторых API содержится ряд различных событий, в некоторых — событий нет. Свойства обработчика, позволяющие запускать функции при совершении какого-либо события по большей части перечислены в нашем материале отдельного раздела "Обработчики событий (Event handlers)".

Мы уже видели несколько обработчиков событий, используемых в нашем примере с Web Audio API выше:

```js
// play/pause audio
playBtn.addEventListener("click", () => {
  // check if context is in suspended state (autoplay policy)
  if (audioCtx.state === "suspended") {
    audioCtx.resume();
  }

  // if track is stopped, play it
  if (playBtn.getAttribute("class") === "paused") {
    audioElement.play();
    playBtn.setAttribute("class", "playing");
    playBtn.textContent = "Pause";
    // if track is playing, stop it
  } else if (playBtn.getAttribute("class") === "playing") {
    audioElement.pause();
    playBtn.setAttribute("class", "paused");
    playBtn.textContent = "Play";
  }
});

// if track ends
audioElement.addEventListener("ended", () => {
  playBtn.setAttribute("class", "paused");
  playBtn.textContent = "Play";
});
```

### У них есть дополнительные средства безопасности там, где это необходимо

Функциональность WebAPI подвержена тем же соображениям безопасности, что и JavaScript или другие веб-технологии (например, same-origin policy), но иногда они содержат дополнительные механизмы защиты. К примеру, некоторые из наиболее современных WebAPI работают только со страницами, обслуживаемыми через HTTPS в связи с передачей конфиденциальных данных (примеры: Service Workers и Push).

К тому же, некоторые WebAPI запрашивают разрешение от пользователя, как только к ним происходит вызов в коде. Например, Notifications API запрашивает разрешение для показа диалогого окна:

![Запрос разрешения на показ уведомлений](/ru/docs/Learn/JavaScript/Client-side_web_APIs/Introduction/notification-permission.png)

Web Audio и HTMLMediaElement API подчиняются механизму безопасности, который называется политикой автозапуска (en-US). Это означает, что вы не можете автоматически воспроизводить звук при загрузки страницы — вы должны разрешить своим пользователям инициировать воспроизведения звука с помощью какого-либо элемента управления, например, кнопки. Сделано это, потому что автовоспроизведение звука обычно очень раздражает, и мы не должны подвергать этому наших пользователей.

Итоги
-----

На данном этапе, у вас должно сформироваться представление о том, что такое API, как они работают и как вы можете применить их в своём JavaScript-коде. Вам наверняка не терпится начать делать по-настоящему интересные вещи с конкретными API, так вперёд! В следующий раз мы рассмотрим работу с документом с помощью Document Object Model (DOM).

* Обзор: Client-side web APIs
* Далее


Введение в асинхронный JavaScript - Изучение веб-разработки

Введение в асинхронный JavaScript
=================================

* Обзор: Asynchronous
* Далее

В этой статье мы кратко остановимся на проблемах, связанных с синхронным Javascript, а также ознакомимся с несколькими асинхронными методами, демонстрирующими как они могут помочь нам подобные проблемы решить.

| Необходимое условие: | Базовая компьютерная грамотность, достаточное понимание основ JavaScript. |
| --- | --- |
| Цель: | Ознакомиться с тем, что такое асинхронный JavaScript, чем он отличается от синхронного и в каких случаях используется. |

Синхронный JavaScript
---------------------

Большая часть функциональности, которую мы рассматривали в предыдущих обучающих модулях, является синхронной — вы запускаете какой-то код, а результат возвращается, как только браузер может его вернуть. Давайте рассмотрим простой пример, как это работает:

```js
const btn = document.querySelector("button");
btn.addEventListener("click", () => {
  alert("You clicked me!");

  let pElem = document.createElement("p");
  pElem.textContent = "This is a newly-added paragraph.";
  document.body.appendChild(pElem);
});
```

В этом блоке кода команды выполняются одна за другой:

1. Получаем ссылку на элемент `<button>`, который уже есть в DOM.
2. Добавляем к кнопке обработчик события `click` так, что при нажатии на неё:
    1. Выводится сообщение `alert()`.
    2. После закрытия сообщения создаём элемент `<p>` (абзац).
    3. Затем добавляем в абзац текст.
    4. В конце добавляем созданный абзац в тело документа.

Пока выполняется каждая операция, ничего больше не может произойти — обработка (отображение) документа приостановлена. Так происходит, потому что JavaScript является однопоточным. В каждый момент времени может выполняться только одна команда, обрабатываемая в единственном — главном потоке. Все остальные действия блокируются до окончания выполнения текущей команды.

Так и в примере выше: после нажатия кнопки абзац не сможет появиться пока не будет нажата кнопка OK в окне сообщения. Попробуйте сами:
```

<button>Нажми меня</button>
```

**Примечание:** Важно помнить, что `alert()`, хоть и часто используется для демонстрации синхронных блокирующих операций, сильно не рекомендован к использованию в реальных приложениях.

Асинхронный JavaScript
----------------------

По причинам, упомянутым ранее (например, относящимся к блокировке), множество Web API особенностей теперь используют асинхронный код, особенно те,что имеют доступ к внешним устройствам или получают от них некоторые ресурсы, такие как получение файла из сети, запрос к базе данных и получение данных из базы, доступ к потоковому видео на веб-камере, просмотр дисплея на гарнитуре виртуальной реальности.

Почему трудно работать, используя синхронный код? Давайте посмотрим на небольшой пример. Когда вы получаете картинку с сервера, вы не можете мгновенно вернуть результат. Это значит что следующий (псевдо) код не сработает:

```js
let response = fetch("myImage.png");
let blob = response.blob();
// display your image blob in the UI somehow
```

Это происходит потому что вы не знаете сколько времени займёт загрузка картинки, следовательно, когда вы начнёте выполнять вторую строку кода, сгенерируется ошибка (возможно, периодически, возможно, каждый раз), потому что response ещё не доступен. Вместо этого, ваш код должен дождаться возвращения response до того, как попытается выполнить дальнейшие инструкции.

Есть два типа стиля асинхронного кода, с которыми вы столкнётесь в коде JavaScript, старый метод — колбэки (callbacks) и более новый — промисы (promises). В следующих разделах мы познакомимся с каждым из них.

Асинхронные колбэки
-------------------

Асинхронные колбэки — это функции, которые определяются как аргументы при вызове функции, которая начнёт выполнение кода на заднем фоне. Когда код на заднем фоне завершает свою работу, он вызывает колбэк-функцию, оповещающую, что работа сделана, либо оповещающую о трудностях в завершении работы. Обратные вызовы — немного устаревшая практика, но они все ещё употребляются в некоторых старомодных, но часто используемых API.

Пример асинхронного колбэка вторым параметром `addEventListener()` (как мы видели выше):

```js
btn.addEventListener("click", () => {
  alert("You clicked me!");

  let pElem = document.createElement("p");
  pElem.textContent = "This is a newly-added paragraph.";
  document.body.appendChild(pElem);
});
```

Первый параметр — тип обрабатываемого события, второй параметр — колбэк-функция, вызываемая при срабатывании события.

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

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

```js
function loadAsset(url, type, callback) {
  const xhr = new XMLHttpRequest();
  xhr.open("GET", url);
  xhr.responseType = type;

  xhr.onload = function () {
    callback(xhr.response);
  };

  xhr.send();
}

function displayImage(blob) {
  const objectURL = URL.createObjectURL(blob);

  const image = document.createElement("img");
  image.src = objectURL;
  document.body.appendChild(image);
}

loadAsset("coffee.jpg", "blob", displayImage);
```

Мы создали функцию `displayImage()`, которая представляет blob, переданный в неё, как объект URL, и создаёт картинку, в которой отображается URL, добавляя её в элемент документа `<body>`. Однако, далее мы создаём функцию `loadAsset()`, которая принимает колбэк-функцию в качестве параметра, вместе с URL для получения данных и типом контента. Для получения данных из URL используется `XMLHttpRequest` (часто сокращается до аббревиатуры "XHR") , перед тем как передать ответ в колбэк-функцию для дальнейшей обработки. В этом случае колбэк-функция ждёт, пока XHR закончит загрузку данных (используя обработчик события `onload` (en-US)) перед отправкой данных в колбэк-функцию.

Колбэк-функции универсальны — они не только позволяют вам контролировать порядок, в котором запускаются функции и данные, передающиеся между ними, они также позволяют передавать данные различным функциям, в зависимости от обстоятельств. Вы можете выполнять различные действия с загруженным ответом, такие как `processJSON()`, `displayText()`, и другие.

Заметьте, что не все колбэк-функции асинхронны — некоторые запускаются синхронно. Например, при использовании `Array.prototype.forEach()` для перебора элементов массива (запустите пример, и посмотрите исходный код):

```js
const gods = ["Apollo", "Artemis", "Ares", "Zeus"];

gods.forEach(function (eachName, index) {
  console.log(index + ". " + eachName);
});
```

В этом примере мы перебираем массив с именами греческих богов и выводим индексы и значения в консоль. Ожидаемый параметр для `forEach()` — это Колбэк-функция, которая содержит два параметра: ссылку на имя массива и значения индексов. Однако эта функция не ожидает никаких действий — она запускается немедленно.

Промисы
-------

Промисы — новый стиль написания асинхронного кода, который используется в современных Web API. Хорошим примером является `fetch()` API, который современнее и эффективнее чем `XMLHttpRequest`. Посмотрим на краткий пример, из нашей статьи Fetching data from the server:

```js
fetch("products.json")
  .then((response) => {
    if (!response.ok) {
      throw new Error(`HTTP error: ${response.status}`);
    }
    return response.json();
  })
  .then((json) => initialize(json))
  .catch((err) => console.error(`Fetch problem: ${err.message}`));
```

**Примечание:** вы можете посмотреть законченную версию на github (посмотрите исходный код и запустите пример).

В примере видно, как `fetch()` принимает один параметр — URL ресурса, который нужно получить из сети, — и возвращает промис. Промис — это объект, представляющий асинхронную операцию, выполненную удачно или неудачно. Он представляет собой как бы промежуточное состояние. По сути, это способ браузера сказать: "я обещаю вернуться к вам с ответом как можно скорее", поэтому в дословном переводе "промис" (promise) означает "обещание".

Может понадобиться много времени, чтобы привыкнуть к данной концепции; это немного напоминает Кота Шрёдингера в действии. Ни один из возможных результатов ещё не произошёл, поэтому операция fetch в настоящее время ожидает результата. Далее у нас есть три блока кода следующих сразу после `fetch()`:

* Два `then()` блока. Оба включают в себя функцию обратного вызова, которая запустится, если предыдущая операция закончилась успешно, и каждая колбэк-функция принимает на вход результат предыдущей успешно выполненной операции, таким образом вы можете выполнять операции последовательно. Каждый `.then()` блок возвращает новый promise, это значит что вы можете объединять в цепочки блоки `.then()`, таким образом можно выполнить несколько асинхронных операций по порядку, одну за другой.
* `catch()` блок описывается в конце и будет запущен если какой-либо `.then()` блок завершится с ошибкой — это аналогично синхронному `try...catch`, ошибка становится доступной внутри `catch()`, что может быть использовано для сообщения пользователю о типе возникшей ошибки. Однако синхронный `try...catch` не будет работать с promise, хотя будет работать с async/await, с которыми вы познакомитесь позже.

**Примечание:** вы узнаете намного больше о promise позже в этом модуле, так что не волнуйтесь если вы что-нибудь не поняли.

### Очередь событий

Асинхронные операции, такие как промисы, помещаются в **очередь событий**, которая запускается после завершения обработки основного потока, чтобы они *не блокировали* выполнение JavaScript-кода. Поставленные в очередь операции завершатся как можно скорее, а затем вернут свои результаты в среду JavaScript.

### Промисы и колбэк-функции

Промисы имеют некоторое сходство со старомодными колбэк-функциями. По сути, они являются возвращаемым объектом, к которому вы присоединяете колбэк-функции, вместо того, чтобы передавать колбэки в функцию.

Тем не менее, промисы сделаны специально для обработки асинхронных операций, и имеют много преимуществ по сравнению с колбэками:

* Вы можете объединить несколько асинхронных операций вместе, используя несколько операций`.then()`, передавая результат одного в следующий в качестве входных данных. Это гораздо сложнее сделать с колбэками, которые часто заканчиваются массивным «адом колбэков» (также известным как callback hell).
* Обратные вызовы Promise всегда вызываются в строгом порядке, который они помещают в очередь событий..
* Обработка ошибок намного лучше — все ошибки обрабатываются одним блоком `.catch ()` в конце блока, а не обрабатываются индивидуально на каждом уровне «пирамиды».
* Промисы избегают инверсии управления, в отличие от колбэков, которые теряют полный контроль над тем, как будет выполняться функция при передаче колбэка в стороннюю библиотеку.
Природа асинхронного кода
-------------------------

Давайте рассмотрим пример, который дополнительно иллюстрирует природу асинхронного кода, показывая, что может произойти, когда мы не полностью осознаем порядок выполнения кода, и проблемы, связанные с попыткой трактовать асинхронный код как синхронный. Следующий пример довольно похож на тот, что мы видели раньше. Одно из отличий состоит в том, что мы включили ряд операторов `console.log_static` чтобы проиллюстрировать порядок, в котором, как вы думаете, будет выполняться код.

```js
console.log("Starting");
let image;

fetch("coffee.jpg")
  .then((response) => {
    console.log("It worked :)");
    return response.blob();
  })
  .then((myBlob) => {
    const objectURL = URL.createObjectURL(myBlob);
    image = document.createElement("img");
    image.src = objectURL;
    document.body.appendChild(image);
  })
  .catch((error) => {
    console.log(
      "There has been a problem with your fetch operation: " + error.message,
    );
  });

console.log("All done!");
```

Браузер начнёт выполнение кода, увидит первый консольный оператор `(Starting)` и выполнит его, а затем создаст переменную `image`.

Затем он переместится на следующую строку и начнёт выполнять блок `fetch ()`, но, поскольку `fetch ()` выполняется асинхронно без блокировки, выполнение кода продолжается после кода, связанного с промисом, тем самым достигая окончательного оператора (`All done!`) и выводя его на консоль.

Только после того, как блок `fetch ()` полностью завершит работу и доставит свой результат через блоки `.then ()`, мы наконец увидим второе сообщение `console.log ()` (`It worked ;)`). Таким образом, сообщения появились не в том порядке, который вы могли ожидать:

* Starting
* All done!
* It worked :)

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

```js
console.log("registering click handler");

button.addEventListener("click", () => {
  console.log("get click");
});

console.log("all done");
```

Этот пример очень схож с предыдущим в своём поведении — первое и третье сообщения `console.log ()` будут показаны немедленно, но второе будет заблокировано, пока кто-то не нажмёт кнопку мыши. Предыдущий пример работает аналогичным образом, за исключением того, что в этом случае второе сообщение блокируется цепочкой промисов, получая ресурс, а затем отображая его на экране, а не щелчком мыши.

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

Чтобы увидеть это в действии попробуйте изменить третий вызов `console.log ()` следующим образом:

```js
console.log("All done! " + image + "displayed.");
```

Теперь вместо третьего сообщения должна возникнуть следующая ошибка:
```

TypeError: image is undefined; can't access its "src" property
```

Это происходит потому, что в то же время браузер пытается запустить третий `console.log()`, блок `fetch()` ещё не закончил выполнение, поэтому переменная `image` ещё не имеет значения.

**Примечание:** Из соображений безопасности вы не можете применять `fetch()` к файлам из вашей локальной системы (или запустить другие такие операции локально); чтобы запустить локально пример выше вам необходимо запустить его через локальный веб-сервер.

Активное обучение: сделайте все это асинхронно!
-----------------------------------------------

Чтобы исправить проблемный пример с `fetch()` и заставить все три сообщения `console.log()` появиться в желаемом порядке, вы можете также запустить третье сообщение `console.log()` асинхронно. Этого можно добиться, переместив его внутрь другого блока `.then()` присоединённого к концу второго, или просто переместив его внутрь второго блока `then()`. Попробуйте исправить это.

Заключение
----------

В своей основной форме JavaScript является синхронным, блокирующим, однопоточным языком, в котором одновременно может выполняться только одна операция. Но веб-браузеры определяют функции и API, которые позволяют нам регистрировать функции, которые не должны выполняться синхронно, а должны вызываться асинхронно, когда происходит какое-либо событие (время, взаимодействие пользователя с мышью или получение данных по сети, например). Это означает, что вы можете позволить своему коду делать несколько вещей одновременно, не останавливая и не блокируя основной поток.

Будем ли мы запускать код синхронно или асинхронно, будет зависеть от того, что мы пытаемся сделать.

Есть моменты, когда мы хотим, чтобы все загружалось и происходило прямо сейчас. Например, при применении некоторых пользовательских стилей к веб-странице вы хотите, чтобы стили применялись как можно быстрее.

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

* Обзор: Asynchronous
* Далее


Делаем асинхронное программирование более простым с async и await - Изучение веб-разработки

Делаем асинхронное программирование более простым с async и await
=================================================================

* Назад
* Обзор: Asynchronous
* Далее  (en-US)

В ECMAScript версии 2017 появились async functions и ключевое слово `await` (ECMAScript Next support in Mozilla). На самом деле, эти функции есть ничего иное как синтаксический сахар над Promises и Generator functions (ts39). С их помощью легче писать и читать асинхронный код, ведь они позволяют использовать привычный синхронный стиль написания. В этой статье мы на базовом уровне разберёмся в их устройстве.

| Примечания: | Чтобы лучше понять материал, желательно перед чтением ознакомиться с основами JavaScript, асинхронными операциями вообще и объектами Promises. |
| --- | --- |
| Цель материала: | Научить писать современный асинхронный код с использованием Promises и async functions. |

Основы async/await
------------------

### Ключевое слово async

 Ключевое слово `async` позволяет сделать из обычной функции (function declaration или function expression) асинхронную функцию (async function). Такая функция делает две вещи:
 - Оборачивает возвращаемое значение в Promise
 - Позволяет использовать ключевое слово await (см. дальше)

Попробуйте выполнить в консоли браузера следующий код:

```js
function hello() {
  return "Hello";
}
hello();
```

Функция возвращает "Hello" — ничего необычного, верно?

Но что если мы сделаем её асинхронной? Проверим:

```js
async function hello() {
  return "Hello";
}
hello();
```

Как было сказано ранее, вызов асинхронной функции возвращает объект Promise.

Вот пример с async function expression:

```js
const hello = async function () {
  return "Hello";
};
hello();
```

Также можно использовать стрелочные функции:

```js
const hello = async () => {
  return "Hello";
};
```

Все они в общем случае делают одно и то же.

Чтобы получить значение, которое возвращает Promise, мы как обычно можем использовать метод `.then()`:

```js
hello().then((value) => console.log(value));
```

или ещё короче

```js
hello().then(console.log);
```

Итак, ключевое слово `async`, превращает обычную функцию в асинхронную и результат вызова функции оборачивает в Promise. Также асинхронная функция позволяет использовать в своём теле ключевое слово `await`, о котором далее.

### Ключевое слово await

Асинхронные функции становятся по настоящему мощными, когда вы используете ключевое слово `await` — по факту, **`await` работает только в асинхронных функциях**. Мы можем использовать `await` перед promise-based функцией, чтобы остановить поток выполнения и дождаться результата её выполнения (результат Promise). В то же время, остальной код нашего приложения не блокируется и продолжает работать.

Вы можете использовать `await` перед любой функцией, которая возвращает Promise, включая Browser API функции.

Небольшой пример:

```js
async function hello() {
  return (greeting = await Promise.resolve("Hello"));
}

hello().then(alert);
```

Конечно, на практике код выше бесполезен, но в учебных целях он иллюстрирует синтаксис асинхронных функций. Теперь давайте перейдём к реальным примерам.

Переписываем Promises с использованием async/await
--------------------------------------------------

Давайте посмотрим на пример из предыдущей статьи:

```js
fetch("coffee.jpg")
  .then((response) => {
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    } else {
      return response.blob();
    }
  })
  .then((myBlob) => {
    const objectURL = URL.createObjectURL(myBlob);
    const image = document.createElement("img");
    image.src = objectURL;
    document.body.appendChild(image);
  })
  .catch((e) => {
    console.log(
      "There has been a problem with your fetch operation: " + e.message,
    );
  });
```

К этому моменту вы должны понимать как работают Promises, чтобы понять все остальное. Давайте перепишем код используя async/await и оценим разницу.

```js
async function myFetch() {
  const response = await fetch("coffee.jpg");

  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  } else {
    const myBlob = await response.blob();

    const objectURL = URL.createObjectURL(myBlob);
    const image = document.createElement("img");
    image.src = objectURL;
    document.body.appendChild(image);
  }
}

myFetch().catch((e) => {
  console.log(
    "There has been a problem with your fetch operation: " + e.message,
  );
});
```

Согласитесь, что код стал короче и понятнее — больше никаких блоков `.then()` по всему скрипту!

Так как ключевое слово `async` заставляет функцию вернуть Promise, мы можем использовать гибридный подход:

```js
async function myFetch() {
  const response = await fetch("coffee.jpg");
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  } else {
    return await response.blob();
  }
}

myFetch()
  .then((blob) => {
    const objectURL = URL.createObjectURL(blob);
    const image = document.createElement("img");
    image.src = objectURL;
    document.body.appendChild(image);
  })
  .catch((e) => console.log(e));
```

Можете попрактиковаться самостоятельно, или запустить наш live example (а также source code).

### Минуточку, а как это все работает?

Вы могли заметить, что мы обернули наш код в функцию и сделали её асинхронной с помощью `async`. Это было обязательно – нам надо создать контейнер, внутри которого будет запускаться асинхронный код, и будет возможность дождаться его результата с помощью await, не блокируя остальной код нашего скрипта.

Внутри `myFetch()` находится код, который слегка напоминает версию на Promise, но есть важные отличия. Вместо того, чтобы писать цепочку блоков `.then()` мы просто использует ключевое слово `await` перед вызовом promise-based функции и присваиваем результат в переменную. Ключевое слово `await` говорит JavaScript runtime приостановить код в этой строке, не блокируя остальной код скрипта за пределами асинхронной функции. Когда вызов promise-based функции будет готов вернуть результат, выполнение продолжится с этой строки дальше.

Пример:

```js
const response = await fetch("coffee.jpg");
```

Значение Promise, которое вернёт `fetch()` будет присвоено переменной `response` только тогда, когда оно будет доступно – парсер делает паузу на данной строке дожидаясь этого момента. Как только значение доступно, парсер переходит к следующей строке, в которой создаётся объект `Blob` из результата Promise. В этой строке, кстати, также используется `await`, потому что метод `.blob()` также возвращает Promise. Когда результат готов, мы возвращаем его наружу из `myFetch()`.

Обратите внимание, когда мы вызываем `myFetch()`, она возвращает Promise, поэтому мы можем вызвать `.then()` на результате, чтобы отобразить его на экране.

К этому моменту вы наверное думаете "Это реально круто!", и вы правы – чем меньше блоков `.then()`, тем легче читать код.

### Добавляем обработку ошибок

Чтобы обработать ошибки у нас есть несколько вариантов.

Мы можем использовать синхронную `try...catch` структуру с `async`/`await`. Вот изменённая версия первого примера выше:

```js
async function myFetch() {
  try {
    const response = await fetch("coffee.jpg");

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    } else {
      const myBlob = await response.blob();
      const objectURL = URL.createObjectURL(myBlob);
      const image = document.createElement("img");
      image.src = objectURL;
      document.body.appendChild(image);
    }
  } catch (e) {
    console.log(e);
  }
}

myFetch();
```

В блок `catch() {}` передаётся объект ошибки, который мы назвали `e`; мы можем вывести его в консоль, чтобы посмотреть детали: где и почему возникла ошибка.

Если вы хотите использовать гибридный подход (пример выше), лучше использовать блок `.catch()` после блока `.then()` вот так:

```js
async function myFetch() {
  const response = await fetch("coffee.jpg");
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  } else {
    return await response.blob();
  }
}

myFetch()
  .then((blob) => {
    const objectURL = URL.createObjectURL(blob);
    const image = document.createElement("img");
    image.src = objectURL;
    document.body.appendChild(image);
  })
  .catch((e) => console.log(e));
```

Так лучше, потому что блок `.catch()` словит ошибки как из асинхронной функции, так и из Promise. Если бы мы использовали блок `try`/`catch`, мы бы не словили ошибку, которая произошла в самой `myFetch()` функции.

Вы можете посмотреть оба примера на GitHub:

* simple-fetch-async-await-try-catch.html (смотреть source code)
* simple-fetch-async-await-promise-catch.html (смотреть source code)
Await и Promise.all()
---------------------

Как вы помните, асинхронные функции построены поверх promises, поэтому они совместимы со всеми возможностями последних. Мы легко можем подождать выполнение `Promise.all()`, присвоить результат в переменную и все это сделать используя синхронный стиль.

Версия с `async`/`await` (смотрите live demo и source code) выглядит так:

```js
async function fetchAndDecode(url, type) {
  const response = await fetch(url);

  let content;

  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  } else {
    if (type === "blob") {
      content = await response.blob();
    } else if (type === "text") {
      content = await response.text();
    }

    return content;
  }
}

async function displayContent() {
  const coffee = fetchAndDecode("coffee.jpg", "blob");
  const tea = fetchAndDecode("tea.jpg", "blob");
  const description = fetchAndDecode("description.txt", "text");

  const values = await Promise.all([coffee, tea, description]);

  const objectURL1 = URL.createObjectURL(values[0]);
  const objectURL2 = URL.createObjectURL(values[1]);
  const descText = values[2];

  const image1 = document.createElement("img");
  const image2 = document.createElement("img");
  image1.src = objectURL1;
  image2.src = objectURL2;
  document.body.appendChild(image1);
  document.body.appendChild(image2);

  const para = document.createElement("p");
  para.textContent = descText;
  document.body.appendChild(para);
}

displayContent().catch((e) => console.log(e));
```

Взгляните на строку с `Promise.all()`:

```js
const values = await Promise.all([coffee, tea, description]);
```

С помощью `await` мы ждём массив результатов всех трёх Promises и присваиваем его в переменную `values`. Это асинхронный код, но он написан в синхронном стиле, за счёт чего он гораздо читабельнее.

Мы должны обернуть весь код в асинхронную функцию, `displayContent()`, и мы не сильно сэкономили на количестве кода, но мы извлекли код блока `.then()`, за счёт чего наш код стал гораздо чище.

Для обработки ошибок мы добавили блок `.catch()` для функции `displayContent()`; Это позволило нам отловить ошибки в обоих функциях.

**Примечание:** Мы также можем использовать синхронный блок `finally` внутри асинхронной функции, вместо асинхронного `.finally()`, чтобы получить информацию о результате нашей операции — смотрите в действии в нашем live example (смотрите source code).

Недостатки async/await
----------------------

Асинхронные функции с `async`/`await` бывают очень удобными, но есть несколько замечаний, о которых полезно знать.

`async`/`await` позволяет вам писать код в синхронном стиле. Ключевое слово `await` блокирует приостанавливает выполнение promise-based функции до того момента, пока promise примет статус fulfilled. Это не блокирует код за пределами вашей асинхронной функции, тем не менее важно помнить, что внутри асинхронной функции поток выполнения блокируется.

Ваш код может стать медленнее за счёт большого количества awaited promises, которые идут один за другим. Каждый `await` должен дождаться выполнения предыдущего, тогда как на самом деле мы хотим, чтобы наши Promises выполнялись одновременно, как если бы мы не использовали async/await.

Есть подход, который позволяет обойти эту проблему — сохранить все выполняющиеся Promises в переменные, а уже после этого дожидаться (awaiting) их результата. Давайте посмотрим на несколько примеров.

Мы подготовили два примера — slow-async-await.html (см. source code) и fast-async-await.html (см. source code). Они оба начинаются с функции возвращающей promise, имитирующей асинхронность процессов при помощи вызова `setTimeout()`:

```js
function timeoutPromise(interval) {
  return new Promise((resolve, reject) => {
    setTimeout(function () {
      resolve("done");
    }, interval);
  });
}
```

Далее в каждом примере есть асинхронная функция `timeTest()` ожидающая три вызова `timeoutPromise()`:

```js
async function timeTest() {
  await timeoutPromise(3000);
  await timeoutPromise(3000);
  await timeoutPromise(3000);
}
```

В каждом примере функция записывает время начала исполнения и сколько времени понадобилось на исполнение `timeTest()` промисов, вычитая время в момент запуска функции из времени в момент разрешения промисов:

```js
const startTime = Date.now();
timeTest().then(() => {
  const finishTime = Date.now();
  const timeTaken = finishTime - startTime;
  alert("Time taken in milliseconds: " + timeTaken);
});
```

Далее представлена асинхронная функция `timeTest()` различная для каждого из примеров.

В случае с медленным примером `slow-async-await.html`, `timeTest()` выглядит:

```js
async function timeTest() {
  await timeoutPromise(3000);
  await timeoutPromise(3000);
  await timeoutPromise(3000);
}
```

Здесь мы просто ждём все три `timeoutPromise()` напрямую, блокируя выполнение на данного блока на 3 секунды при каждом вызове. Все последующие вызовы вынуждены ждать пока разрешится предыдущий. Если вы запустите первый пример (`slow-async-await.html`), то увидите `alert` сообщающий время выполнения около 9 секунд.

Во втором `fast-async-await.html` примере, функция `timeTest()` выглядит так:

```js
async function timeTest() {
  const timeoutPromise1 = timeoutPromise(3000);
  const timeoutPromise2 = timeoutPromise(3000);
  const timeoutPromise3 = timeoutPromise(3000);

  await timeoutPromise1;
  await timeoutPromise2;
  await timeoutPromise3;
}
```

В данном случае мы храним три объекта `Promise` в переменных, каждый из которых может разрешиться независимо от других.

Ниже мы ожидаем разрешения промисов из объекта в результат. Так как они были запущенны одновременно, блокируя поток, то и разрешатся одновременно. Если вы запустите второй пример вы увидите `alert`, сообщающий время выполнения около 3 секунд.

Важно не забывать о быстродействии применяя `await`, проверяйте количество блокировок.

Async/await class methods
-------------------------

В качестве последнего замечания, вы можете использовать `async` перед методами классов или объектов, вынуждая их возвращать promises. А также `await` внутри методов объявленных таким образом. Посмотрите на пример ES class code, который мы видели в статье object-oriented JavaScript и сравните его с модифицированной (асинхронной) `async` версией ниже:

```js
class Person {
  constructor(first, last, age, gender, interests) {
    this.name = {
      first,
      last,
    };
    this.age = age;
    this.gender = gender;
    this.interests = interests;
  }

  async greeting() {
    return await Promise.resolve(`Hi! I'm ${this.name.first}`);
  }

  farewell() {
    console.log(`${this.name.first} has left the building. Bye for now!`);
  }
}

const han = new Person("Han", "Solo", 25, "male", ["Smuggling"]);
```

Первый метод класса теперь можно использовать таким образом:

```js
han.greeting().then(console.log);
```

Browser support (Поддержка браузерами)
--------------------------------------

Одним из критериев при принятии решения об использовании `async`/`await` является поддержка старых браузеров. `async`/`await`, как и Promises, доступны в большинстве современных браузеров. В основном, проблемы с поддержкой могут возникнуть в Internet Explorer и Opera Mini.

Если вы хотите использовать `async`/`await`, но при этом беспокоитесь о поддержке старых браузеров, можно рассмотреть возможность использования библиотеки BabelJS — она позволяет писать приложения с использованием новейшего JavaScript и может автоматически предоставлять полифиллы, которые работают в старых браузерах, в качестве запасных вариантов.

Заключение
----------

Вот пожалуй и все — `async`/`await` позволяют писать асинхронный код, который легче читать и поддерживать. Даже учитывая, что поддержка со стороны браузеров несколько хуже, чем у `promise.then`, всё же стоит обратить на него внимание.

* Назад
* Обзор: Asynchronous
* Далее  (en-US)