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

В чем преимущество switchMap?

1.3 Junior🔥 111 комментариев
#JavaScript Core

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

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

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

switchMap — управление вложенными потоками данных

switchMap — это оператор RxJS, который преобразует значения observable и переключается на новый inner observable, отписываясь от предыдущего. Это мощный инструмент для управления асинхронными операциями.

Что такое switchMap

import { switchMap } from "rxjs/operators";
import { from } from "rxjs";

// Базовый пример
from([1, 2, 3]).pipe(
  switchMap(value => {
    console.log(`Переключились на значение ${value}`);
    return from([value * 10, value * 20]);
  })
).subscribe(result => console.log(result));
// Вывод: 10, 20, 20, 40, 30, 60

Ключевое преимущество: Отписка от предыдущего observable

import { switchMap, interval } from "rxjs";

// ❌ Без switchMap — утечка памяти
function badAutocomplete(searchTerm$: Observable<string>): Observable<Result[]> {
  return searchTerm$.pipe(
    // Каждый поиск запускает новый API запрос
    // Если пользователь быстро меняет поиск, все запросы выполняются
    // и результаты приходят в неправильном порядке
    mergeMap(term => this.api.search(term))
  );
}

// ✅ Со switchMap — отписываемся от предыдущих запросов
function goodAutocomplete(searchTerm$: Observable<string>): Observable<Result[]> {
  return searchTerm$.pipe(
    // При новом значении отписываемся от предыдущего запроса
    // Только последний запрос будет завершён и выдаст результаты
    switchMap(term => this.api.search(term)),
    debounceTime(300) // Ждём 300мс перед поиском
  );
}

switchMap vs mergeMap vs concatMap

// Симуляция трёх асинхронных операций
function delayedRequest(id: number): Observable<string> {
  return of(`Результат ${id}`).pipe(
    delay(1000) // Задержка 1 сек
  );
}

// ❌ mergeMap — выполняет ВСЕ параллельно
const clicks$ = fromEvent(button, "click");

clicks$.pipe(
  mergeMap(() => delayedRequest(Math.random()))
).subscribe(console.log);

// Если кликнуть 3 раза, то:
// Все 3 запроса выполняются параллельно
// Результаты приходят в случайном порядке
// Может привести к сетевым перегрузкам

// ✅ switchMap — переключается на новый, отписываясь от старого
clicks$.pipe(
  switchMap(() => delayedRequest(Math.random()))
).subscribe(console.log);

// Если кликнуть 3 раза быстро, то:
// При втором клике первый запрос отменяется
// При третьем клике второй запрос отменяется
// Только последний запрос завершится и выдаст результат

// ✅ concatMap — выполняет по очереди
clicks$.pipe(
  concatMap(() => delayedRequest(Math.random()))
).subscribe(console.log);

// Если кликнуть 3 раза, то:
// Первый запрос выполняется полностью (1 сек)
// Потом второй (ещё 1 сек)
// Потом третий (ещё 1 сек)
// Результаты приходят в правильном порядке, но медленнее

Практический пример: Поиск пользователя

import { Subject, switchMap, debounceTime, distinctUntilChanged, catchError } from "rxjs";

@Component({
  selector: "user-search",
  template: `
    <input 
      [formControl]="searchControl" 
      placeholder="Поиск пользователя"
    />
    <div *ngIf="users$ | async as users">
      <div *ngFor="let user of users">{{ user.name }}</div>
    </div>
  `
})
export class UserSearchComponent {
  searchControl = new FormControl("");
  
  users$ = this.searchControl.valueChanges.pipe(
    debounceTime(300), // Ждём 300мс перед поиском
    distinctUntilChanged(), // Ищем только если значение изменилось
    switchMap(term => {
      if (!term) {
        return of([]); // Пустой результат если нет поиска
      }
      return this.userService.search(term).pipe(
        catchError(() => of([])) // Обработка ошибок
      );
    })
  );

  constructor(private userService: UserService) {}
}

switchMap в Angular (HTTP запросы)

import { switchMap, mergeMap } from "rxjs/operators";

// Получаем ID поста, затем получаем комментарии
function getPostWithComments(postId$: Observable<number>) {
  return postId$.pipe(
    switchMap(postId => {
      console.log(`Загружаем комментарии для поста ${postId}`);
      return this.http.get(`/api/posts/${postId}/comments`);
    })
  );
}

// Получаем пользователей, затем для каждого получаем его посты
function getUsersWithPosts(users$: Observable<User[]>) {
  return users$.pipe(
    // ❌ Неправильно: вложенные запросы
    mergeMap(users =>
      from(users).pipe(
        mergeMap(user => this.http.get(`/api/users/${user.id}/posts`))
      )
    )
  );
}

// ✅ Правильно: switchMap для переключения между наборами данных
function getFirstUserPosts(firstUser$: Observable<User>) {
  return firstUser$.pipe(
    switchMap(user => this.http.get(`/api/users/${user.id}/posts`))
  );
}

switchMap с React + RxJS

import { useEffect, useState } from "react";
import { of, switchMap, debounceTime, Subject } from "rxjs";

function UserSearch() {
  const [searchTerm, setSearchTerm] = useState("");
  const [results, setResults] = useState<User[]>([]);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    const searchTerm$ = new Subject<string>();

    const subscription = searchTerm$.pipe(
      debounceTime(300),
      switchMap(term => {
        if (!term) return of([]);
        setLoading(true);
        return fetch(`/api/search?q=${term}`)
          .then(r => r.json())
          .finally(() => setLoading(false));
      })
    ).subscribe(data => setResults(data));

    // Отправляем новый поиск
    searchTerm$.next(searchTerm);

    return () => subscription.unsubscribe();
  }, [searchTerm]);

  return (
    <div>
      <input
        value={searchTerm}
        onChange={e => setSearchTerm(e.target.value)}
        placeholder="Поиск..."
      />
      {loading && <p>Загрузка...</p>}
      <ul>
        {results.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}

Основные преимущества switchMap

  1. Отменяет предыдущие запросы — экономит пропускную способность сети
  2. Предотвращает race condition — результаты не путаются
  3. Чистит ресурсы — отписывается от ненужных observable
  4. Упрощает логику — не нужно вручную отслеживать запросы
  5. Последний результат — выдаёт только результат последнего запроса
  6. Работает с любыми async операциями — HTTP, таймеры, события

Важно: Отписка

// switchMap АВТОМАТИЧЕСКИ отписывается от inner observable
// при переключении, но вам всё равно нужно отписаться от outer

const subscription = source$.pipe(
  switchMap(value => innerObservable$)
).subscribe(console.log);

// ❌ Плохо: утечка памяти
// subscription не отписан

// ✅ Хорошо: явная отписка
ngOnDestroy() {
  subscription.unsubscribe();
}

// ✅ Ещё лучше: использовать takeUntil
source$.pipe(
  takeUntil(this.destroy$),
  switchMap(value => innerObservable$)
).subscribe(console.log);
В чем преимущество switchMap? | PrepBro