Чем отличаются операторы switchMap, mergeMap, concatMap и exhaustMap в RxJS?
Signals и RxJS
Все четыре — операторы «высшего порядка»: для каждого значения источника они запускают внутренний Observable (например, HTTP-запрос) и сводят результаты в один поток. Отличаются тем, что делать, пока предыдущий внутренний поток ещё не завершён.
switchMap — переключиться на новый
Отписывается от предыдущего внутреннего потока и берёт последний. Подходит для поиска по мере ввода и навигации: нужен только актуальный результат, устаревшие запросы отменяются.
search$.pipe(
debounceTime(300),
switchMap(q => this.http.get(`/api?q=${q}`)),
);mergeMap — параллельно
Запускает все внутренние потоки одновременно, результаты приходят в порядке завершения. Подходит для независимых операций, где порядок не важен (отправка нескольких файлов). Может перегрузить сервер: у него есть параметр concurrent.
concatMap — по очереди
Ставит внутренние потоки в очередь и выполняет строго по порядку, каждый после завершения предыдущего. Подходит для операций, где важен порядок (последовательные записи, сохранение изменений).
exhaustMap — игнорировать, пока занят
Пока внутренний поток выполняется, новые значения источника игнорируются. Подходит для защиты от двойного клика: кнопка «Отправить», вход в систему.
click$.pipe(exhaustMap(() => this.http.post('/api/order', body)));Сравнение
- `switchMap` — Параллельность: отменяет предыдущий; Порядок результатов: актуальный; Что с новыми значениями: заменяют старые
- `mergeMap` — Параллельность: да; Порядок результатов: по завершении; Что с новыми значениями: запускаются сразу
- `concatMap` — Параллельность: нет; Порядок результатов: исходный; Что с новыми значениями: ждут в очереди
- `exhaustMap` — Параллельность: нет; Порядок результатов: исходный; Что с новыми значениями: отбрасываются
Коротко для собеседования
switchMap — только последнее (поиск), mergeMap — всё параллельно, concatMap — по очереди с сохранением порядка, exhaustMap — игнорирует новые, пока идёт текущее (защита от повторных кликов).