Positive.JS

RxJS - Часть 1, Введение в реактивное программирование

A = B + C

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

Существует множество определений понятия реактивности, которые разнятся от запутанных до очень запутанных и, поначалу, не вносят особой ясности. Хотя, если говорить абстракциями, все не так уж и сложно: A = B + C

В случае императивного программирования после момента присваивания, переменная A будет существовать независимо от B и С на всем протяжении жизненного цикла программы, в то время как в реактивном программировании при изменении B или C, значение A будет автоматически пересчитано

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

Потоки

Поток данных не что иное, как событие, повторяющеесе во времени. Для начала, можно представить в виде потока последовательность кликов пользователя:

поток

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

Поток выше изображен в виде марбл-диаграммы, но есть и альтернативный способ визуализации событий - c помощью ASCII кодировки. RxJS 5 позволяет использовать такие диаграммы при написании тестов (Observable.combineLatest.spec)

 --a---b-c---d---X---|->
 
 a, b, c, d события значения
 X ошибка
 | завершение потока
 ---> шкала времени

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

  • UI события, любое взаимодействие пользователя с интерфейсом
  • Запросы к серверу
  • События устройства, пуш уведомления, системные нотификации
  • Веб хуки

Асинхронное событие - событие, которое произойдет в какой-либо неопределенный момент в будущем

Есть и другие варианты объяснения асинхронности, например, с использованием пиццы и World of Warcraft

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

[1,2,3,4,5].forEach(item => { 
    item; // событие-значение 
});

Перебрав массив из 5 элементов, мы определили синхронный поток, в котором были инициированы 5 событий значение и одно завершение потока. Процесс аналогичен сессии пользователя, который 5 раз кликнул по кнопке и закрыл страницу. Таким же образом в роли последовательного потока может выступать не только массив, но любой итерабельной объект: Set, Map, String и т.д.

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

Все - поток

Чем обрабатывать потоки?

Как мы выяснили, наше приложение - инкубатор всевозможных потоков. Давайте поверхностно рассмотрим средства их обработки. Далее речь пойдет об асинхронных событиях, про варианты обработки синхронных событий можно посмотреть здесь: Lodash, Ramda

  • EventTarget интерфейс обработки различных UI событий в javascript. Клик пользователя, например, можно поймать следующим образом:
document.getElementById('button').addEventListener('click', event => {
    // обработка
});
  • EventEmitter используется при построении асинхронно-событийной архитектуры в Node.js
 const emitter = new EventEmitter();
 
 emitter.on('event', data => {
  // обработка
 });
 
 emitter.emit('event', data); // пуш события
 
  const cb = function(err, data) {
      if (err) {
        // событие-ошибка
      }
      
      // событие-значения
  }
   
  fs.readFile('data.txt', cb); 
 
  • Promise так же используются для однократных асинхронных вычислений
const request = new Promise((resolve, reject) => {
    // имитация запроса к серверу
    window.setTimeout(() => { resolve(true); }, 3000);
});

request
    .then(response => { ... }) // событие-значения 
    .catch(err => { ... }) // событие-ошибка 
  • Генераторы - функции с возможностью приостановить свое выполнение на некоторое время, после чего возобновить вновь. Своего рода имплементация корутин в javascript, позволяющая писать асинхронный код в синхронном стиле
function request(url) {
    ajaxCall(url, response => { it.next(response); });
}

function *main() {
    const result = yield request('http://some.url');
    const data = JSON.parse(result); // событие-значение
}

const it = main();
it.next(); 

И экспериментальные, еще не принятые на сегодняшний день в стандарт инструменты, которые, тем не менее, можно использовать посредством Babel полифиллов:

  • Async functions были предложены как часть стандарта ES2016. В данный момент фича находится на завершающем этапе принятия - Stage 4. Подробно с асинхронными функциями можно ознакомиться в соответствующем предложении
  • Async generators изначально были задуманы для реализации обработки многократных асинхронных событий. В данный момент фича получила более компактное описание на Stage 3,
    но, возможно, будет реализована в виде ECMAScript Observable - имплементации Observable для стандарта ES2016 (теме Observable посвещена следующая статья из этой серии)

Причем тут RxJS?

Было бы удобно иметь единый интерфейс взаимодействия с любым средством событийной обработки. И такой интерфейс предоставляет бибилиотека RxJS (Reactive Extensions for Javascript).

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

Почитать и посмотреть по теме

<< Предыдущая статья