Первый шаг в мир RxJS: знакомство с Observables

Осваивать что-то новое всегда сложно, особенно когда перед тобой сталкиваются потоки терминов, функций и вовсе не очевидных на первый взгляд концепций. Давайте разбираться на простых примерах, попробуем выстроить мини-план по освоению этой темы. Пока я подумываю о том, чтобы написать цикл статей в подобном ключе на темы, которые подсказывает мне мой опыт общения с новичками в отрасли (при условии, что тема и подача вызовут интерес).
Почему вообще стоит изучать RxJS? Реактивное программирование становится всё более популярным подходом в разработке современных приложений, особенно там, где важны асинхронные данные — пользовательский ввод, работа с веб-сокетами, обработка событий или запросы к API. RxJS активно используется в таких фреймворках, как Angular, где его знание практически обязательно для работы с компонентами, сервисами и потоками данных.
Но даже за пределами строгих требований фреймворков, RxJS — это мощный инструмент, который помогает работать с асинхронностью гораздо удобнее и гибче, чем традиционные подходы с колбэками или промисами. Освоив его, вы не только получите преимущество в обслуживании сложных приложений, но и откроете для себя совершенно новый взгляд на управление данными и событиями.
Проблема многих новичков, впервые сталкивающихся с темой подписок, заключается в обилии тем и нюансов, связанных с этим направлением. Тонны статей, дискуссий, документаций на темы, аля «RxJS mergeMap vs switchMap vs concatMap vs exhaustMap» могут вызвать головную боль уже в первые пятнадцать минут) Потому первый мой совет: ограничьте список тем и осваиваемых инструментов до самого минимума, декомпозиция важна везде и всегда — слона надо есть по частям. Так начинающий учиться шахматам сначала разбирает каждую фигуру в отдельности, запоминает возможности и нюансы, постепенно переходя к глобальной стратегии игры. Поэтому давайте смахнем все фигуры с доски и оставим там только пешку.
Представьте себя подписчиком на журналы или газеты
Основная идея Observables — это наблюдение за событиями и реакция на них. Чтобы сделать это понятнее, давайте сравним Observables с системой подписки на журналы или газеты:
У вас есть издательство (Источник данных).
Вы хотите получать газеты каждый день (Подписчик).
Вы подписываетесь на доставку газет.
Пока вы подписаны, вам регулярно доставляют газеты (данные).
Обратите внимание на ключевые моменты:
Газеты приходят только после подписки.
Вы можете отписаться, и подписки больше не будет.
Иногда газеты могут перестать приходить (например, издавать журнал перестали).
Observable в программировании работает по той же схеме:
Есть источник данных (например, поток событий, результаты запроса на сервер, пользовательский ввод).
Вы (подписчик) хотите следить за этими данными и реагировать на их изменения.
Вы «подписываетесь» на источник с помощью subscribe ().
Пока вы подписаны, вы получаете данные или уведомления от источника.
Переходим к программированию: основа работы Observable
Observable — это объект, который производит данные или уведомления. Данные могут быть любого типа: числа, строки, массивы, результаты HTTP-запросов. И вот тут начинается интересное: традиционно на этом моменте принято показывать примеры в духе
import { Observable } from 'rxjs';
const observable = new Observable(subscriber => {
subscriber.next(1); // Отправляем значение 1
subscriber.next(2); // Отправляем значение 2
subscriber.next(3); // Отправляем значение 3
subscriber.complete(); // Сообщаем, что поток завершён
});
observable.subscribe({
next: value => console.log(value), // Что делать с каждым значением
complete: () => console.log('Поток завершён') // Уведомление о завершении
});
// Вывод в консоли:
// 1
// 2
// 3
// Поток завершён
Да только мой опыт показывает, что это вообще не работает с неофитами — здесь происходит разом слишком много вещей, которые приходится либо долго объяснять построчно, либо предлагать зажмуриться и обращать внимание только на «основную мысль», игнорируя «прочие несущественные детали». Но мы же договорились, что будем пытаться найти самую маленькую тему, потому я предлагаю ознакомиться с оператором from()
и строить обучение от него. Почему так? Да просто потому что он «смахивает с доски» все вопросы, связанные с созданием потоков, выбросом событий, завершением подписок и иже с ними. Возвращаясь к примеру с газетами: мы пока хотим разобраться с тем, как получать нашу любимую газету каждый день, а не с тем, как построить типографию и наладить её работу.
Поэтому договоримся, что для наших базовых экспериментов нам нужно будет только две вещи: оператор from и какая-нибудь коллекция объектов, которую мы будем придумывать на ходу.
Где запускать код?
Если у вас не вызывает вопросов идея поднять локально мусорное приложение для опытов, то можно пойти по этому пути, но я бы предложил обратить внимание на online-инструменты — они в очередной раз избавят нас от лишних деталей окружения. Поискать что-то удобное для себя можно просто в google по запросу «typescript online run».
От себя могу предложить посмотреть на playcode. Здесь нам нужно просто добавить библиотеку rxjs в разделе packages (слева) и можно творить.
Создаем поток чисел
Давайте перефразируем предыдущий пример с Observable:
import { from } from 'rxjs';
const obs = from([1,2,3]);
obs.subscribe(value => console.log(value));
// Вывод в консоли:
// 1
// 2
// 3
Вот и весь базовый пример, которому, однако, стоит уделить достаточно времени. Пример с типографией здесь не очень уместен в силу простоты происходящего, поэтому давайте переключимся на людей) Бывает в разговоре мы задеваем какую-то очень интересную собеседнику тему и он начинает потоком вываливать на нас свои знания/мнения по этому вопросу (может это офисные сплетни о коллегах, а может лор Warhammer’a, тут уж как повезет). Так и obs держит в себе «знания» о числах 1,2,3, чтобы вывалить их на любого обратившегося. Стоит нам подписаться на obs мы последовательно получаем всю коллекцию элементов. При этом, если к obs подключится другой слушатель, он вновь получит всю коллекцию целиком, все как в жизни, этого парня просто не заткнуть)
import { from } from 'rxjs';
const obs = from([1,2,3]);
obs.subscribe(value => console.log(value));
obs.subscribe(value => console.log(value));
// Вывод в консоли:
// 1
// 2
// 3
// 1
// 2
// 3
Что дальше?
А дальше начните эксперименты с этим микро-кодом, постепенно расширяя функционал. Меняйте данные и их типы, поработайте, например, с коллекцией string’ов или объектов. Свыкнитесь с мыслью, что все эти подписки — лишь всеядный инструмент, действующий по шаблону, а вовсе не супер-сложный космический шаттл.
Дальше стоит рассмотреть логику pipe()
с двумя операторами — map()
и filter()
.
Сам pipe()
не стоит особо рассматривать — это просто функция, позволяющая нам изменять поток данных, принимая соответствующие операторы (начнём мы с двух упомянутых).
filter
Тот парень со сплетнями вряд ли будет рассказывать вам сплетни о вас самих, эти истории он прибережёт для другого собеседника, вот вам и идея фильтрации: не всегда мы будем работать с полным потоком данных, иногда нам нужно отсеять не актуальные данные.
import { from,filter } from 'rxjs';
const obs = from([1,2,3]).pipe(filter(n => n > 2));
obs.subscribe(value => console.log(value));
// Вывод в консоли:
// 3
filter
устанавливает условие для публикации данных, и вновь то же пожелание: экспериментируйте, меняйте типы данных, условия, освойтесь полноценно с идеями фильтрации, чтобы позже приходить к другим операторам с большей осознанностью и пониманием.
Стоит здесь обратить внимание на важный нюанс: pipe может быть включен на всей подписке целиком, либо только на конкретном подписчике.
import { from,filter } from 'rxjs';
const obs = from([1,2,3]).pipe(filter(n => n > 2));
obs.subscribe(value => console.log(value));
obs.subscribe(value => console.log(value));
// Вывод в консоли:
// 3
// 3
Здесь сам источник фильтрует данные и никто из подписчиков не может получить нефильтрованный набор — наш сплетник не готов делиться всем, что знает.
import { from,filter } from 'rxjs';
const obs = from([1,2,3]);
obs.pipe(filter(n => n > 2)).subscribe(value => console.log(value));
obs.subscribe(value => console.log(value));
// Вывод в консоли:
// 3
// 1
// 2
// 3
В этом же примере один из слушателей и знать ничего не хочет про числа меньше двух, при том, что obs
честно отдает весь набор.
map
Рассказывая истории, сложно удержаться от того, чтобы что-нибудь приукрасить) Вот тут нам и поможет map
, способный изменять поток на лету.
import { from,map } from 'rxjs';
const obs = from([1,2,3]).pipe(map(n => n * 2));
obs.subscribe(value => console.log(value));
// Вывод в консоли:
// 2
// 4
// 6
map берёт каждое значение (например, число, текст, объект) из потока.
Преобразует его по вашему запросу.
Передаёт дальше уже изменённую версию данных.
Бесконечный поток данных для экспериментов
Предыдущие шаги помогают нам освоится с механизмами работы rxjs, но, в действительности, не особо похожи на что-то асинхронное — код выполняется мгновенно, нет никаких ожиданий и задержек. Поэтому стоит включить в наш стартовый набор еще одну функцию: interval
. Она создает бесконечный поток чисел от 0, выдавая их через заданный промежуток времени в миллисекундах.
Давайте создадим Observable, который каждую секунду будет выдавать новое значение:
import { interval } from 'rxjs';
const timerObservable = interval(1000); // Каждую секунду
timerObservable.subscribe(value => {
console.log(`Прошла секунда: ${value}`);
});
// Вывод в консоли:
// Прошла секунда: 0
// Прошла секунда: 1
// Прошла секунда: 2
// Прошла секунда: 3
// Прошла секунда: 4
Этот генератор позволит вам немного расширить базу для экспериментов, наряду упомянутыми выше функциями.
Заключение
Главное, что стоит помнить: освоение таких мощных инструментов, как RxJS, требует времени. И это нормально. Серьёзно, никто не становится экспертом за один день.
Не стремитесь сразу понять всё и применять десятки операторов. Среди них легко потеряться, если пытаться «объять необъятное». Начинайте с самого простого, с тех тем, которые уже умещаются в вашем понимании: экспериментируйте с from ()/interval ()/filter/map пока не почувствуете, что код в рамках этих тем пишется легко и не вызывает трудностей в чтении, следующие шаги будут не в пример легче.
Помните, что каждую сложную тему можно разбить на простые кусочки. Осознанное постепенно движение — это путь к тому, чтобы однажды RxJS стал для вас не запутанной магией, а удобным и мощным инструментом, который так и хочется использовать.
Не бойтесь делать ошибки, задавать вопросы и возвращаться к простым примерам. Все прогрессируют по-разному, но каждый из нас начинал когда-то с базовых вещей.
В следующих статьях мы разберём, как сделать Observables ещё более мощными с помощью других операторов, таких как switchMap, mergeMap и иже с ними и попробуем понять, как вообще осваивать весь этот огромный список операторов.