В чем разница между switchMap и mergeMap?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Разница между switchMap и mergeMap
switchMap и mergeMap - это два оператора RxJS для трансформации потоков (streams) при работе с асинхронными операциями. Они отличаются тем, как они обрабатывают множество одновременных подписок.
mergeMap
mergeMap подписывается на все внутренние наблюдаемые (observables) и объединяет их результаты. Все запросы выполняются параллельно.
Особенности:
- Выполняет все асинхронные операции
- Не отменяет предыдущие запросы
- Идеален, когда порядок и все результаты важны
- Может привести к race conditions
import { of } from 'rxjs';
import { mergeMap, delay } from 'rxjs/operators';
of(1, 2, 3)
.pipe(
mergeMap(value => {
console.log(`Запрос ${value} запущен`);
return of(`Результат ${value}`).pipe(delay(1000));
})
)
.subscribe(value => console.log(value));
// Результат:
// Запрос 1 запущен
// Запрос 2 запущен
// Запрос 3 запущен
// (через 1 сек) Результат 1
// (через 1 сек) Результат 2
// (через 1 сек) Результат 3
switchMap
switchMap отменяет предыдущую подписку и переключается на новую наблюдаемую. Только последний запрос будет обработан.
Особенности:
- Отменяет предыдущие запросы
- Обрабатывает только последний результат
- Предотвращает race conditions
- Идеален для поисковых запросов, фильтрации
import { of } from 'rxjs';
import { switchMap, delay } from 'rxjs/operators';
of(1, 2, 3)
.pipe(
switchMap(value => {
console.log(`Запрос ${value} запущен`);
return of(`Результат ${value}`).pipe(delay(1000));
})
)
.subscribe(value => console.log(value));
// Результат:
// Запрос 1 запущен
// Запрос 2 запущен (отменяет запрос 1)
// Запрос 3 запущен (отменяет запрос 2)
// (через 1 сек) Результат 3
Визуальная разница
// mergeMap - все запросы выполняются
Input: 1 2 3
| | |---> API 1 (1000ms)
| |---> API 2 (1000ms)
|---> API 3 (1000ms)
Output: 1
2
3
// switchMap - новый запрос отменяет старый
Input: 1 2 3
| | |---> API 3 (1000ms)
| |
|
Output: 3 (только последний результат)
Практический пример: поиск пользователя
Пользователь вводит текст в поле поиска. С каждым вводом отправляется запрос на сервер:
Используя mergeMap (ПЛОХО)
implement OnInit {
@ViewChild('searchInput') searchInput: HTMLInputElement;
results$ = new Observable();
ngOnInit() {
this.results$ = fromEvent(this.searchInput, 'input')
.pipe(
map(event => (event.target as HTMLInputElement).value),
mergeMap(query =>
this.userService.search(query)
)
);
}
}
// Пользователь вводит: 'a', 'al', 'ale', 'alex'
// Запросы:
// 1. /api/search?q=a
// 2. /api/search?q=al (не дожидаясь результата от запроса 1)
// 3. /api/search?q=ale (не дожидаясь результатов от 1 и 2)
// 4. /api/search?q=alex (не дожидаясь...)
//
// Проблема: результаты приходят не в порядке ввода!
// Результат от 'ale' может придти после 'alex'
Используя switchMap (ХОРОШО)
implement OnInit {
@ViewChild('searchInput') searchInput: HTMLInputElement;
results$ = new Observable();
ngOnInit() {
this.results$ = fromEvent(this.searchInput, 'input')
.pipe(
map(event => (event.target as HTMLInputElement).value),
switchMap(query =>
this.userService.search(query)
)
);
}
}
// Пользователь вводит: 'a', 'al', 'ale', 'alex'
// Запросы:
// 1. /api/search?q=a (отменяется)
// 2. /api/search?q=al (отменяется запрос 1)
// 3. /api/search?q=ale (отменяется запрос 2)
// 4. /api/search?q=alex (отменяется запрос 3) - выполняется
//
// Результат: только один запрос, результаты в правильном порядке
Другие операторы семейства map
concatMap
Выполняет запросы последовательно, в очереди. Ждет завершения предыдущего перед началом следующего.
of(1, 2, 3)
.pipe(
concatMap(value =>
of(`Результат ${value}`).pipe(delay(1000))
)
)
.subscribe(value => console.log(value));
// Результат (последовательно):
// (1сек) Результат 1
// (2сек) Результат 2
// (3сек) Результат 3
exhaustMap
Игнорирует новые запросы, пока предыдущий еще выполняется.
of(1, 2, 3)
.pipe(
exhaustMap(value =>
of(`Результат ${value}`).pipe(delay(1000))
)
)
.subscribe(value => console.log(value));
// Результат:
// (1сек) Результат 1
// 2 и 3 игнорируются
Сравнительная таблица
| Оператор | Поведение | Когда использовать |
|---|---|---|
| mergeMap | Все запросы параллельно | Независимые операции (скачивание файлов) |
| switchMap | Отменяет старый, начинает новый | Поиск, фильтрация, автокомплит |
| concatMap | Последовательно, в очереди | Операции, требующие порядка (оплаты) |
| exhaustMap | Игнорирует новые пока старый выполняется | Кнопка submit (предотвращение двойной отправки) |
Практический пример: infiniteMap vs switchMap
API запрос с debounce (searchMap)
search(query: string): Observable<User[]> {
return this.http.get<User[]>(`/api/users?q=${query}`);
}
ngOnInit() {
this.results$ = fromEvent(this.searchInput, 'input')
.pipe(
debounceTime(300), // ждем 300ms после последнего ввода
map(event => (event.target as HTMLInputElement).value),
switchMap(query => this.search(query)) // используем switchMap
);
}
Загрузка более подробной информации (использование mergeMap)
getUsersWithDetails(userIds: number[]): Observable<UserDetail[]> {
return from(userIds).pipe(
mergeMap(id => this.http.get<UserDetail>(`/api/users/${id}`))
);
}
// Загружаем все детали параллельно
this.userDetails$ = this.getUsersWithDetails([1, 2, 3]);
Важный момент: unsubscribe
свitchMap автоматически отписывает от предыдущей подписки:
const subscription = this.input$
.pipe(
switchMap(query => this.apiCall(query))
)
.subscribe();
// switchMap позаботится об отписке от предыдущей подписки
// Вам нужно отписаться только от основного потока
А mergeMap НЕ отписывает автоматически:
const subscription = this.input$
.pipe(
mergeMap(query => this.apiCall(query))
)
.subscribe();
// Вам нужно тщательнее следить за утечками памяти
// mergeMap будет выполнять все запросы до конца
Заключение
- switchMap - для user input, где новый ввод отменяет старый (поиск, фильтрация)
- mergeMap - для независимых параллельных операций (скачивание файлов, параллельные запросы)
- concatMap - для операций, где порядок критичен (последовательные действия)
- exhaustMap - для предотвращения двойных отправок (кнопка submit)
Выбор правильного оператора предотвращает race conditions и утечки памяти в RxJS приложениях.