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 | Практически везде (, события и др.) |
Пример на 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-операторов.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ