Статьи

Асинхронные API с использованием Fetch API и ES6 Generators

ECMAScript 6 (также известный как ECMAScript 2015 или ES6) привносит в JavaScript ряд новых функций, которые сделают язык подходящим для больших приложений. Одна из этих функций — лучшая поддержка асинхронного программирования с использованием обещаний и генераторов . Другим является добавление API Fetch, целью которого является замена XMLHttpRequest в качестве основы связи с удаленными ресурсами.

Методы API Fetch возвращают объекты Promise ES6, которые можно использовать вместе с генераторами для формирования основы сложных асинхронных операций. Это может быть что угодно: от цепочки асинхронных операций, где каждая операция зависит от значения, возвращенного предыдущей, до асинхронного вызова, который необходимо повторно выполнить на сервере, чтобы получить последнее обновление.

В этой статье мы увидим, как Fetch API можно использовать вместе с генераторами для создания асинхронных API. Fetch API в настоящее время поддерживается в браузерах Chrome, Opera, Firefox и Android . У нас есть полифилл, доступный на GitHub для неподдерживаемых браузеров.

Как всегда, код для этой статьи можно найти в нашем репозитории GitHub, а в нижней части статьи приведена демонстрация финальной техники .

Генераторы для асинхронных операций

Совет: Если вам нужно освежить в памяти, что такое генераторы и как они работают, ознакомьтесь: ECMAScript 2015: Генераторы и итераторы

Итак, как мы можем использовать генераторы для выполнения асинхронных операций? Что ж, если мы проанализируем работу генераторов, мы найдем ответ.

Функция генератора, реализующая итератор, имеет следующую структуру:

 function *myIterator(){ while(condition){ //calculate next value to return yield value; } } в function *myIterator(){ while(condition){ //calculate next value to return yield value; } } 

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

Мы можем заново представить вышеприведенную функцию без цикла while следующим образом:

 function *myIterator(){ //calculate value 1 yield value1; //calculate value 2 yield value2; ... //calculate value n yield valuen; } 

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

Использование генераторов с Fetch API

Совет: Для обновления по Fetch API, ознакомьтесь с: Введение в Fetch API

Как упоминалось ранее, Fetch API предназначен для замены XMLHttpRequest . Этот новый API обеспечивает контроль над каждой частью HTTP-запроса и возвращает обещание, которое разрешается или отклоняется на основе ответа от сервера.

Длинный опрос

Одним из случаев, когда Fetch API и генераторы могут использоваться вместе, является длительный опрос . Длинный опрос — это метод, при котором клиент продолжает отправлять запросы на сервер, пока не получит ответ. Генераторы могут быть использованы в таком случае, чтобы продолжать давать ответы, пока ответ не содержит данных.

Чтобы имитировать длительные опросы, я включил в пример кода Express REST API, который отправляет информацию о погоде для города после пяти попыток. Ниже приводится REST API:

 var polls=0; app.get('/api/currentWeather', function(request, response){ console.log(polls, polls<5); if(polls < 5){ console.log("sending...empty"); polls++; response.send({}); } else{ console.log("sending...object"); response.send({ temperature: 25, sky: "Partly cloudy", humid: true }); polls = 0; } }); 

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

 function *pollForWeatherInfo(){ while(true){ yield fetch('/api/currentWeather',{ method: 'get' }).then(function(d){ var json = d.json(); return json; }); } } в function *pollForWeatherInfo(){ while(true){ yield fetch('/api/currentWeather',{ method: 'get' }).then(function(d){ var json = d.json(); return json; }); } } 

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

 function runPolling(generator){ if(!generator){ generator = pollForWeatherInfo(); } var p = generator.next(); p.value.then(function(d){ if(!d.temperature){ runPolling(generator); } else { console.log(d); } }); } runPolling(); 

Как мы видим здесь, первый вызов функции runPolling создает объект generator . next метод возвращает объект со свойством value которое в нашем случае содержит обещание, возвращаемое методом fetch . Когда это обещание разрешится, оно будет содержать либо пустой объект (возвращаемый, если переменная polls меньше 5), либо объект, содержащий требуемую информацию.

Далее мы проверяем свойство temperature этого объекта (которое указывало бы на успех). Если его нет, мы передаем объект generator обратно на следующий вызов функции (чтобы не потерять состояние генератора) или выводим значение объекта на консоль.

Чтобы увидеть это в действии, возьмите код из нашего репозитория , установите зависимости, запустите сервер, затем перейдите по адресу http: // localhost: 8000 . Вы должны увидеть следующие результаты в оболочке:

 0 true sending...empty 1 true sending...empty 2 true sending...empty 3 true sending...empty 4 true sending...empty 5 false sending...object 

И сам объект вошел в консоль браузера.

Несколько зависимых асинхронных вызовов

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

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

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

  • Получить подробную информацию об организации
  • Если организация существует, получите репо организации
  • Получить вкладчиков для одного из репо организации (выбирается случайным образом)

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

 function wrapperOnFetch(url){ var headers = new Headers(); headers.append('Accept', 'application/vnd.github.v3+json'); var request = new Request(url, {headers: headers}); return fetch(request).then(function(res){ return res.json(); }); } 

Следующая функция использует вышеуказанную функцию и выдает обещание для каждого вызова:

 function* gitHubDetails(orgName) { var baseUrl = "https://api.github.com/orgs/"; var url = baseUrl + orgName; var reposUrl = yield wrapperOnFetch(url); var repoFullName = yield wrapperOnFetch(reposUrl); yield wrapperOnFetch(`https://api.github.com/repos/${repoFullName}/contributors`); } 

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

 var generator = gitHubDetails("aspnet"); generator.next().value.then(function (userData) { //Update UI return generator.next(userData.repos_url).value.then(function (reposData) { return reposData; }); }).then(function (reposData) { //Update UI return generator.next(reposData[randomIndex].full_name).value.then(function (selectedRepoCommits) { //Update UI }); }); 

Чтобы увидеть это в действии, как описано выше, возьмите код из нашего репозитория , установите зависимости, запустите сервер, затем перейдите по адресу http: // localhost: 8000 . Или просто ознакомьтесь с демонстрацией ниже (попробуйте запустить ее заново).

демонстрация

Вывод

В этой статье я продемонстрировал, как можно использовать API Fetch в сочетании с генераторами для создания асинхронных API. ECMAScript 6 привнесет в язык множество новых функций, и поиск изобретательных способов их объединения и использования их возможностей часто может привести к выдающимся результатам. Но что вы думаете? Это техника, которую мы можем начать использовать в наших приложениях сегодня? Я хотел бы услышать ваши мысли в комментариях.