Комментарии (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
- Отменяет предыдущие запросы — экономит пропускную способность сети
- Предотвращает race condition — результаты не путаются
- Чистит ресурсы — отписывается от ненужных observable
- Упрощает логику — не нужно вручную отслеживать запросы
- Последний результат — выдаёт только результат последнего запроса
- Работает с любыми 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);