Шаблоны ES6: Преобразование коллбэков в промисы
JavaScript

Шаблоны ES6: Преобразование коллбэков в промисы

Оригинал: ES6 Patterns: Converting Callbacks to Promises, Ben McCormick
Я пишу код, используя новые возможности ECMAScript 2015 (более известного как ES6) с января. В течение года я использовал несколько новых шаблонов, которые, как мне кажется, делают мой код лучше. Я хочу поделиться ими в небольших статьях. Я уже писал про чистые функции высшего порядка. В этой статье я сфокусируюсь на работе с асинхронным кодом в ES6.

Основы

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

Простой GET-запрос с использованием XMLHttpRequest:


var request = new XMLHttpRequest();  
request.onload = function() {  
  var data = JSON.parse(this.responseText);  
  //do stuff with data
};  

request.onerror = function() {  
    alert('There was a problem with the request');
}
request.open('get', '/api/foo/bar', true);  
request.send();

То же самое с помощью fetch:


fetch('/api/foo/bar')
    .then(function(data) {  
        return data.json();
    })
    .then(function(jsonData) {
        //do stuff with the data
    })
    .catch(function(e) {
        alert('There was a problem with the request');
    });

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

Промисы были доступны пользователям и раньше. Есть множество библиотек промисов, которые в конечном итоге сформировали стандарт, называемый Promises/A+. Promises/A+ совместимые библиотеки — Q, Bluebird или rsvp. Есть также много старых библиотек, которые предоставляют схожие возможности, но не совместимы со спецификацией, например jQuery Deferred.

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

Конвертация кода, основанного на коллбэках, в код, основанный на промисах

Если вы решите использовать промисы, вы сразу столкнетесь с проблемой в современном мире JavaScript. Многие JavaScript API, включая большинство стандартных браузерных API и старые, но популярные библиотеки, такие как jQuery или Backbone, основаны на функциях обратного вызова. Вместо того, чтобы смешивать два различных стиля асинхронного программирования, было бы неплохо просто сконвертировать API, основанный на коллбэках, в API, основанный на промисах? Оказывается, это не так уж сложно. Возьмем для начала простейший пример. Функция setTimeout ожидает некоторое время, а затем выполняет функцию обратного вызова. Стандартное ее использование выглядит так:


function doStuff() {/*...*/}

setTimeout(doStuff, 300);

API, основанный на промисах, для этой функции выглядел был как-то так:


timeout(300).then(doStuff)

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

Вы можете создать A+ совместимый промис с помощью конструктора Promise, который ожидает функцию в качестве аргумента. Эта функция принимает два параметра — функции resolve и reject. Замечательная вещь заключается в том, что «под капотом» эти функции являются коллбэками, но промисы это скрывают.

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


function timeout(delay) {  
    return new Promise(function(resolve, reject) {
        setTimeout(resolve, delay); 
    });
}

Мы не использовали коллбэк reject, поскольку функция setTimeout не предоставляет обработчик ошибки. Мы просто передаем коллбэк resolve в setTimeout. Теперь у нас есть отличная функция, которую можно включать в цепочку промисов.

Переходя к более сложному примеру, давайте возьмем наш код с XMLHttpRequest выше и попытаемся реализовать упрощеный fetch API, используя XMLHttpRequest под капотом. Я буду использовать стрелочные функции ES6, чтобы немного уменьшить шаблон.


const fetch = (url, options = {method:'get'}) => new Promise((resolve, reject) => {  
    let request = new XMLHttpRequest();  
    request.onload = resolve 
    request.onerror = reject;
    request.open(options.method, url, true);  
    request.send();
});

Это упрощенный пример, который не покрывает все возможности fetch, но прекрасно показыает, как легко можно преобразовать API на основе функций обратного вызова в API на основе промисов.

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


const callbackFetch = (url, options, succ, err) => fetch(url, options).then(succ).catch(err);

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

Дополнительные материалы

В бесплатной онлайн версии Exploring ES6 Акселя Райшмайера (Axel Rauschmayer) есть отличная глава, описывающая ES6 Promises API и то, как он соответствует стандарту A+.

Также есть отличная статья в блоге MSDN, которая рассказывает, как асинхронный код в JavaScript эволюционировал на протяжении нескольких лет, в том числе рассмотрены шаблоны async/await из ES2016.

Рассылка
Подпишитесь на рассылку и получайте дайджест новостей и статей.
Никакого спама!
Подписаться