JavaRush /Курсы /Модуль 4: Node.js, Next.js и Angular /RxJS и Observables: концепция, отличие от Promises

RxJS и Observables: концепция, отличие от Promises

Модуль 4: Node.js, Next.js и Angular
17 уровень , 0 лекция
Открыта

1. Введение в Observables

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

Angular (начиная с версии 2 и по сей день) делает ставку на библиотеку RxJS (Reactive Extensions for JavaScript) и её главный инструмент — Observable. Это не просто "ещё одна обёртка для асинхронности", а целая парадигма реактивного программирования, где данные представляются как потоки, а вы можете с ними делать что угодно: фильтровать, трансформировать, комбинировать, реагировать на любые их изменения.

RxJS — это...

RxJS (Reactive Extensions for JavaScript) — это библиотека для работы с асинхронными и событийными потоками данных с помощью Observables. Она даёт вам больше возможностей, чем Promises или обычные события, и позволяет легко управлять даже очень сложными асинхронными сценариями.

Observable — это...

Observable — это такой "источник данных", который со временем может выдавать много значений (а может и ни одного!), и на каждое из них вы можете реагировать. Аналогия: представьте себе радио — вы включили приёмник (подписались), и теперь слушаете музыку (приходят новые значения). Как только надоело — выключили (отписались).

Схематично:


Observable (поток) ---> [значение 1] ---> [значение 2] ---> ... ---> [завершение/ошибка]

В отличие от Promise, Observable может выдавать не одно, а много значений, и вы сами решаете, когда "отключиться" от потока.

2. Сравнение: Observable vs Promise

Давайте разберёмся, чем отличается Observable от Promise на практике и в теории.

Характеристика Promise Observable
Значений за "жизнь" Только одно Много (или ни одного)
Ленивость Нет (исполняется сразу) Да (начинает работать при подписке)
Отмена Нет (можно "забить", но не отменить выполнение) Да (unsubscribe)
Операторы обработки then/catch/finally Огромный набор операторов RxJS
Многократность Можно слушать только один раз Можно подписываться сколько угодно раз
Используется в Angular Для некоторых API Практически везде (
HttpClient
, события и др.)

Пример на Promise


const promise = fetch('https://api.example.com/data');
promise.then(response => response.json())
       .then(data => console.log(data));

// Вы получите только одно значение, и всё — на этом жизнь Promise закончена.

Пример на Observable


import { Observable } from 'rxjs';

const observable = new Observable(subscriber => {
  subscriber.next('Привет!');
  setTimeout(() => subscriber.next('Через секунду!'), 1000);
  setTimeout(() => subscriber.complete(), 1500);
});

observable.subscribe({
  next: value => console.log('Получено:', value),
  complete: () => console.log('Поток завершён')
});

Вы получите несколько значений, и Observable сам сообщит, когда всё закончилось.

3. Создание и подписка на Observable

В Angular вы чаще всего не создаёте Observable "вручную", а получаете их из сервисов (HttpClient, событий и т.д.). Но для понимания давайте создадим свой Observable:


import { Observable } from 'rxjs';

// Создаём Observable
const myObservable = new Observable(subscriber => {
  subscriber.next('Первое значение');
  setTimeout(() => subscriber.next('Второе значение'), 1000);
  setTimeout(() => subscriber.complete(), 2000);
});

// Подписываемся на события
const subscription = myObservable.subscribe({
  next: value => console.log('Получили:', value),
  error: err => console.error('Ошибка:', err),
  complete: () => console.log('Завершено!')
});

// Можно отписаться в любой момент
setTimeout(() => subscription.unsubscribe(), 1500);
  • subscriber.next() — отправляет новое значение подписчику.
  • subscriber.complete() — сообщает, что поток завершён.
  • subscription.unsubscribe() — отписывает вас от потока. После этого значения приходить не будут.

4. Использование Observables в Angular: пример с HTTP

В Angular практически любой асинхронный сервис возвращает Observable. Давайте посмотрим на пример с HTTP-запросом:


import { HttpClient } from '@angular/common/http';
import { Component } from '@angular/core';

@Component({
  selector: 'app-users',
  template: `
    <button (click)="loadUsers()">Загрузить пользователей</button>
    <ul>
      <li *ngFor="let user of users">{{ user.name }}</li>
    </ul>
  `
})
export class UsersComponent {
  users: any[] = [];

  constructor(private http: HttpClient) {}

  loadUsers() {
    this.http.get<any[]>('https://jsonplaceholder.typicode.com/users')
      .subscribe({
        next: data => this.users = data,
        error: err => alert('Ошибка загрузки!'),
        complete: () => console.log('Загрузка завершена!')
      });
  }
}
  • http.get() возвращает Observable, а не Promise.
  • Запрос уйдёт ТОЛЬКО когда вы подпишетесь (subscribe).
  • Вы можете отменить подписку, если, например, компонент уничтожается (об этом — на следующих лекциях).

5. Полезные нюансы

Типы Observables: "холодные" и "горячие"

  • Холодный Observable (cold): начинает работу только при подписке. Каждый подписчик получает "свои" значения.
  • Горячий Observable (hot): поток уже идёт, и новые подписчики просто "вклиниваются" в уже текущий поток.

В Angular HttpClient.get() возвращает холодный Observable — запрос уйдёт только при подписке!

Операторы RxJS: магия для обработки потоков

Одна из главных причин использовать Observables — это богатый набор операторов для обработки, фильтрации, преобразования, комбинирования данных.

Пример: получать только пользователей с именем "Leanne".


import { map, filter } from 'rxjs/operators';

this.http.get<any[]>('https://jsonplaceholder.typicode.com/users')
  .pipe(
    map(users => users.filter(user => user.name === 'Leanne Graham'))
  )
  .subscribe(users => console.log(users));

pipe — позволяет "наслаивать" операторы друг на друга, как бутерброд.

Типичные сценарии использования Observables

  • Получение данных с сервера (HttpClient).
  • Реакция на события пользователя (fromEvent).
  • Работа с таймерами (interval, timer).
  • Комбинирование нескольких потоков данных.
  • Реализация автокомплитов, поиска "на лету" (debounceTime, switchMap и др.).

6. Типичные ошибки начинающих

Ошибка №1: забыли подписаться.
Если вы не вызвали .subscribe(), Observable не начнёт работать. Это частая причина "почему запрос не уходит".

Ошибка №2: не отписались от Observable.
Если вы подписались на "бесконечный" поток (например, события или таймер), но не отписались при уничтожении компонента — получите утечку памяти. В Angular принято отписываться в ngOnDestroy, использовать takeUntil, async pipe и другие техники.

Ошибка №3: путаница с Promise.
Пытаются использовать .then() у Observable — не работает! Для Observable нужен .subscribe().

Ошибка №4: двойная подписка.
Если вы подписываетесь несколько раз на один и тот же Observable, каждый раз может уходить новый HTTP-запрос (для холодных Observable). Это может привести к лишней нагрузке на сервер.

Ошибка №5: игнорирование ошибок.
Не обрабатываете ошибку в .subscribe({ error: ... }) — приложение падает или "молчит" при проблемах с сетью.

Факт с подвохом:
В Angular можно преобразовать Observable в Promise через .toPromise(), но это редко оправдано — вы теряете все преимущества реактивного программирования и RxJS-операторов.

Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ