← Назад к вопросам

В чем разница между switchMap и mergeMap?

2.0 Middle🔥 191 комментариев
#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Разница между 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 приложениях.