Модуль 4 · урок 2 з 13
Створення потоків
Щоб працювати з потоками, їх треба звідкись брати. Функцій створення в RxJS десятки, але в реальному коді ти використовуватимеш шість-сім. Розберемо саме їх — і одразу з прикладами, де вони справді потрібні.
of — готові значення
import { of } from 'rxjs';
of(1, 2, 3).subscribe(console.log); // 1, 2, 3, потім complete
of([1, 2, 3]).subscribe(console.log); // [1,2,3] одним значенням — типова плутанина
// головне застосування: підставити готові дані замість запиту
getTasks(): Observable<Task[]> {
return of(MOCK_TASKS);
}
of — основа всього мок-шару з уроку 11. Він віддає значення синхронно й одразу
завершує потік.
from — із того, що вже є
import { from } from 'rxjs';
from([1, 2, 3]); // три окремі значення (на відміну від of([...]))
from(fetch('/api/tasks')); // з промайса
from('abc'); // 'a', 'b', 'c' — з будь-чого ітерованого
timer і interval — за часом
import { timer, interval } from 'rxjs';
timer(1000); // одне значення через секунду, потім complete
timer(0, 5000); // одразу, далі кожні 5 секунд — не завершується сам
interval(1000); // кожну секунду, перше значення через секунду
interval і timer з другим аргументом працюють, доки їх не зупинити.
Якщо на такий потік підписатись у компоненті й не відписатись, він продовжить крутитись
після того, як компонент зникне з екрана. Це і є класичний витік памʼяті — про нього урок 10.
throwError — контрольована помилка
import { throwError } from 'rxjs';
// саме так: фабрика, а не готовий обʼєкт помилки
throwError(() => new Error('Сервер недоступний'));
У мок-сервісі це спосіб показати, як застосунок поводиться при збої — без справжнього бекенду. Обовʼязкова частина критеріїв: «graceful error handling for mocked async operations».
EMPTY і NEVER
import { EMPTY, NEVER } from 'rxjs';
EMPTY; // одразу complete, жодного значення — зручно повертати з catchError
NEVER; // нічого не робить ніколи — переважно в тестах
fromEvent — події DOM
import { fromEvent } from 'rxjs';
fromEvent<KeyboardEvent>(document, 'keydown').pipe(
filter(e => e.key === 'Escape'),
).subscribe(() => this.close());
Це саме той випадок, коли RxJS дає більше, ніж addEventListener: події можна
фільтрувати, обмежувати частотою й комбінувати з іншими потоками — і все це в одному
місці, з автоматичним прибиранням слухача при відписці.
defer — відкласти створення
import { defer, of } from 'rxjs';
// ❌ дата обчислиться один раз, у момент створення потоку
const wrong$ = of(new Date());
// ✅ дата обчислюватиметься на кожну підписку
const right$ = defer(() => of(new Date()));
defer потрібен щоразу, коли потік має брати актуальний стан у момент
підписки, а не той, що був при створенні. Наприклад, коли URL запиту залежить від поточного
значення фільтра.
HttpClient теж повертає Observable
this.http.get<Task[]>('/api/tasks');
this.http.get<Task[]>('/api/tasks', { params: { status: 'todo' } });
this.http.post<Task>('/api/tasks', payload);
this.http.patch<Task>(`/api/tasks/${id}`, patch);
this.http.delete<void>(`/api/tasks/${id}`);
Це холодний потік, який завершується після відповіді. Дженерик у кутових дужках — те саме
правило з модуля 2: тип відповіді вказується явно, ніяких any.
| Треба | Береш |
|---|---|
| Віддати готові дані | of(value) |
| Розгорнути масив у потік значень | from(array) |
| Обгорнути проміс | from(promise) |
| Зробити щось із затримкою | timer(ms) |
| Періодичне опитування | timer(0, ms) |
| Змоделювати збій | throwError(() => err) |
| Нічого не повернути, але завершити | EMPTY |
| Слухати подію DOM | fromEvent(el, 'click') |
| Порахувати щось у момент підписки | defer(() => …) |