Введение в реактивное программирование
Представь типичное веб-приложение, например интернет-магазин.
- Пользователь нажимает кнопку (показать детали товара).
- Приложение отправляет запрос на медленный сервер и должно ждать ответа.
- Только после получения ответа оно может обновить страницу.
1. Фундаментальная проблема: зачем вообще “реактивное”?
Классический (императивный) подход: приложение блокирует выполнение, пока ждёт ответа от сервера. Это неэффективно! Представь ситуацию в ресторане: ты заказываешь блюдо и затем держишь официанта за рукав, пока оно не будет готово. В это время он не может обслуживать других гостей.
Реактивное решение: приложение говорит серверу: “Отправь мне данные, когда они будут готовы. Я не буду здесь ждать, но оставляю тебе свой номер телефона. Позвони мне, когда что-то будет”. Тем временем оно может заниматься другими задачами.
2. Основной принцип: асинхронные потоки данных
Реактивное программирование строится на двух ключевых идеях:
- Асинхронность: задачи выполняются “в фоне”. Главная программа не блокируется и не ждёт результата.
- Потоки данных (Streams): всё можно рассматривать как поток событий: щелчки мыши, нажатия клавиш, HTTP-запросы, результаты из базы данных и так далее. Эти потоки можно создавать, преобразовывать, фильтровать и комбинировать.
3. Аналогия: модель подписки (газета vs YouTube)
Чтобы понять реактивный способ мышления, сравним два подхода:
| Императивный (модель запроса) | Реактивный (модель подписки) | |
|---|---|---|
| Аналогия | Купить газету в киоске | Подписаться на YouTube-канал |
| Действие | Ты идёшь в киоск и активно просишь последний номер. (Pull) | Ты нажимаешь “Подписаться”. (Push) |
| В будущем | На следующей неделе ты должен вернуться в киоск, чтобы взять новый номер. | Ты получаешь уведомление, как только появляется новое видео. |
| Преимущество | Ты сразу получаешь то, что хочешь. | Ты ничего не пропустишь и не должен постоянно проверять. Контент приходит к тебе. |
Реактивное программирование работает по модели подписки (принцип Push). Ты подписываешься на источник данных и получаешь уведомление, как только появляются новые данные.
4. Главные действующие лица: Observer и Observable
Эта абстракция используется почти во всех реактивных фреймворках (например RxJava, Project Reactor).
-
Observable (или Publisher):
- Кто это? Источник данных. Он производит поток событий или данных.
- Примеры: кнопка (производит события клика), датчик (производит измерения), ответ сервера.
- Задача: отслеживать, кто заинтересован в его данных (его “подписчики”).
-
Observer (или Subscriber):
- Кто это? Потребитель данных. Он заинтересован в данных Observable.
- Примеры: функция, реагирующая на клик; логика, обрабатывающая значение датчика.
- Задача: подписаться на Observable. Должен обработать три вида уведомлений.
5. Три callback-функции: как Observer реагирует
Observer должен сообщить Observable, что делать при каждом событии. Это достигается реализацией трёх методов:
-
onNext(T value)- Когда? Вызывается, когда Observable выдаёт новый элемент данных.
- Что делать? Обработай полученный элемент (например, покажи товары, залогируй значение датчика).
- Аналогия YouTube: было загружено новое видео.
-
onError(Throwable error)- Когда? Вызывается, если произошла ошибка.
- Что делать? Обработка ошибки (например, покажи сообщение об ошибке пользователю, попробуй ещё раз).
- Аналогия YouTube: произошла ошибка при загрузке видео.
-
onComplete()- Когда? Вызывается, когда Observable не будет отправлять больше данных. Поток завершён.
- Что делать? Очистка ресурсов.
- Аналогия YouTube: YouTuber удалил свой канал. Новых видео точно не будет.
6. Пример кода (концептуально)
Допустим, мы хотим логировать каждый щелчок мыши на веб-странице.
Императивный подход (псевдокод)
// Мы активно опрашиваем (PULL)
while (true) {
if (mouseWasClicked()) { // Блокирует, пока не будет клик!
const clickEvent = getClickEvent(); // Получает клик
console.log('Click at: ', clickEvent.position);
}
}
// Проблема: весь цикл блокируется и ждёт только кликов.
Реактивный подход (псевдокод с RxJS)
// Мы подписываемся на источник кликов (PUSH)
fromEvent(document, 'click') // Создаёт Observable из событий клика
.subscribe( // Подписываемся и определяем callback-функции
(clickEvent) => { // onNext: что происходит при каждом клике
console.log('Click at: ', clickEvent.clientX, clickEvent.clientY);
},
(error) => { // onError: что происходит при ошибке
console.error('Something went wrong: ', error);
},
() => { // onComplete: что происходит в конце (здесь никогда не вызывается, так как клики не заканчиваются)
console.log('No more clicks. Goodbye!');
}
);
// Преимущество: главный поток не блокируется и может выполнять другие задачи.
// Callback-функция вызывается только когда действительно есть событие.
7. Почему это так мощно? Операторы!
Настоящая сила кроется в возможности манипулировать этими потоками данных с помощью операторов, похожих на map, filter, reduce в функциональном программировании.
filter: пропускать только определённые события. (Например, только клики внутри конкретного div)map: трансформировать события. (Например, преобразовать событие клика в объект{x: 10, y: 20})debounceTime: “дебаунсить” события. (Например, обработать только последний клик в быстрой последовательности, что отлично для поиск-полей!)merge: объединить несколько потоков. (Например, клики и нажатия клавиш в один общий поток)
fromEvent(document, 'click')
.pipe(
filter(event => event.target.id === 'myButton'), // Только клики на #myButton
debounceTime(250), // Подожди 250мс между кликами
map(event => { return {x: event.clientX, y: event.clientY}; }) // Трансформируй событие
)
.subscribe(coord => console.log('Clicked at: ', coord));
Итоги
- Реактивное программирование – это парадигма для неблокирующей, асинхронной обработки потоков данных.
- Оно следует модели Push или подписки: потребитель данных (Observer) подписывается на источник данных (Observable).
- Observer реагирует на три вида событий: новые данные (
onNext), ошибка (onError) и завершение (onComplete). - Благодаря операторам (таким как
filter,map) потоки данных можно элегантно трансформировать и комбинировать. - Преимущества: лучшее использование ресурсов (без блокировок), элегантная обработка асинхронных событий, простая композиция сложных операций.

