raceWith - adopte le flux le plus rapide
L'opérateur raceWith adopte seulement le premier Observable qui émet une valeur à partir de l'Observable original et des autres Observables spécifiés, par la suite, il ignore les autres Observable. C'est la version Pipeable Operator de la race de Creation Function.
🔰 Syntaxe de base et utilisation
import { timer } from 'rxjs';
import { raceWith, map } from 'rxjs';
const slow$ = timer(5000).pipe(map(() => 'Lentement. (5secondes)'));
const medium$ = timer(3000).pipe(map(() => 'Normale (3secondes)'));
const fast$ = timer(2000).pipe(map(() => 'Rapide (2secondes)'));
slow$
.pipe(raceWith(medium$, fast$))
.subscribe(console.log);
// Sortie: Rapide (2secondes)- Seul l'Observable qui a émis la valeur en premier (
fast$dans cet exemple) est gagnant et continue avec les flux suivants. - Les autres Observable sont ignorés.
🌐 Documentation officielle de RxJS - raceWith
💡 Modèle d'utilisation typique.
- Mise en œuvre du délai d'attente : faire courir le délai d'attente contre le processus principal.
- Traitement de secours : adopter le traitement le plus rapide parmi plusieurs sources de données.
- Interaction avec l'utilisateur** : adopter le plus rapide des clics et de l'auto-progression.
🧠 Exemples de code pratique (avec interface utilisateur)
Voici un exemple de course entre le clic manuel et la minuterie de progression automatique et l'adoption de la plus rapide.
import { fromEvent, timer } from 'rxjs';
import { raceWith, map, take } from 'rxjs';
// Création d'une zone de sortie
const output = document.createElement('div');
output.innerHTML = '<h3>raceWith Exemples pratiques de:</h3>';
document.body.appendChild(output);
// Création de boutons
const button = document.createElement('button');
button.textContent = 'Procéder manuellement (5Cliquer dans les secondes qui suivent)';
document.body.appendChild(button);
// Message d'attente
const waiting = document.createElement('div');
waiting.textContent = '5Cliquez sur le bouton dans les secondes qui suivent ou attendez l'avance automatique....';
waiting.style.marginTop = '10px';
output.appendChild(waiting);
// Flux de clics manuels
const manualClick$ = fromEvent(button, 'click').pipe(
take(1),
map(() => '👆 Le clic manuel a été sélectionné!')
);
// Minuterie de progression automatique (5(après 2,5 secondes)
const autoProgress$ = timer(5000).pipe(
map(() => '⏰ La progression automatique a été sélectionnée!')
);
// Exécution de la course
manualClick$
.pipe(raceWith(autoProgress$))
.subscribe((winner) => {
waiting.remove();
button.disabled = true;
const result = document.createElement('div');
result.innerHTML = `<strong>${winner}</strong>`;
result.style.color = 'green';
result.style.fontSize = '18px';
result.style.marginTop = '10px';
output.appendChild(result);
});- Le clic manuel est adopté si le bouton est cliqué dans les 5 secondes.
- Après 5 secondes, la progression automatique est adoptée.
- Le plus rapide est le gagnant**, le plus lent est ignoré.
🔄 Différences avec la Creation Function race.
Différences de base.
import { timer } from 'rxjs';
import { raceWith, map } from 'rxjs';
const slow$ = timer(5000).pipe(map(() => 'Lentement. (5secondes)'));
const medium$ = timer(3000).pipe(map(() => 'Normale (3secondes)'));
const fast$ = timer(2000).pipe(map(() => 'Rapide (2secondes)'));
slow$
.pipe(raceWith(medium$, fast$))
.subscribe(console.log);
// Sortie: Rapide (2secondes)Exemples spécifiques d'utilisation
Si vous souhaitez une course simple, Creation Function est la meilleure solution.
import { race, timer } from 'rxjs';
import { map } from 'rxjs';
const server1$ = timer(3000).pipe(map(() => 'Serveur1Réponse du'));
const server2$ = timer(2000).pipe(map(() => 'Serveur2Réponse du'));
const server3$ = timer(4000).pipe(map(() => 'Serveur3Réponse du'));
// Simple et facile à lire
race(server1$, server2$, server3$).subscribe(response => {
console.log('Adopté:', response);
});
// Sortie: Adopté: Serveur2Réponse du (Le plus rapide2secondes)Si vous voulez ajouter un processus de conversion au flux principal, le Pipeable Operator est recommandé.
import { fromEvent, timer, of } from 'rxjs';
import { raceWith, map, switchMap, catchError } from 'rxjs';
const searchButton = document.createElement('button');
searchButton.textContent = 'Recherche';
document.body.appendChild(searchButton);
const output = document.createElement('div');
output.style.marginTop = '10px';
document.body.appendChild(output);
// Généraliste: Demandes de recherche des utilisateurs
const userSearch$ = fromEvent(searchButton, 'click').pipe(
switchMap(() => {
output.textContent = 'Recherche...';
// APISimuler un appel (3(prend quelques secondes)
return timer(3000).pipe(
map(() => '🔍 Résultats de la recherche: 100Résultats'),
catchError((err: unknown) => of('❌ Erreur survenue'))
);
})
);
// ✅ Pipeable OperatorEdition - Terminé en une seule fois
userSearch$
.pipe(
raceWith(
// Délai d'attente (en2secondes)
timer(2000).pipe(
map(() => '⏱️ Délai d'attente (s): La recherche prend trop de temps')
)
)
)
.subscribe(result => {
output.textContent = result;
});
// ❌ Creation FunctionEdition - Le mainstream doit être écrit séparément
import { race } from 'rxjs';
race(
userSearch$,
timer(2000).pipe(
map(() => '⏱️ Délai d'attente (s): La recherche prend trop de temps')
)
).subscribe(result => {
output.textContent = result;
});Mise en œuvre d'un traitement de secours
import { timer, throwError } from 'rxjs';
import { raceWith, map, mergeMap, catchError, delay } from 'rxjs';
import { of } from 'rxjs';
// UICréation
const output = document.createElement('div');
output.innerHTML = '<h3>Acquisition de données (avec repli)</h3>';
document.body.appendChild(output);
const button = document.createElement('button');
button.textContent = 'Début de l'acquisition des données';
document.body.appendChild(button);
const statusArea = document.createElement('div');
statusArea.style.marginTop = '10px';
output.appendChild(statusArea);
button.addEventListener('click', () => {
statusArea.textContent = 'Pendant l'acquisition...';
// PrincipalAPI(Priorité):Temps々Échec
const mainApi$ = timer(1500).pipe(
mergeMap(() => {
const success = Math.random() > 0.5;
if (success) {
return of('✅ PrincipalAPIAcquisition réussie à partir de');
} else {
return throwError(() => new Error('PrincipalAPIÉchec'));
}
}),
catchError((err: unknown) => {
console.log('PrincipalAPIÉchec, cédé au repli...');
// Retard en cas d'erreur, cédé à la solution de repli
return of('').pipe(delay(10000));
})
);
// ✅ Pipeable OperatorEdition - PrincipalAPIAjouter le repli à
mainApi$
.pipe(
raceWith(
// SauvegardeAPI(repli):Légèrement plus lent mais fiable
timer(2000).pipe(
map(() => '🔄 SauvegardeAPIRécupéré à partir de')
)
)
)
.subscribe(result => {
if (result) {
statusArea.textContent = result;
statusArea.style.color = result.includes('Principal') ? 'green' : 'orange';
}
});
});Adopter le plus rapidement à partir de plusieurs sources de données.
import { timer, fromEvent } from 'rxjs';
import { raceWith, map, mergeMap } from 'rxjs';
const output = document.createElement('div');
output.innerHTML = '<h3>MultipleCDNChargement le plus rapide à partir de</h3>';
document.body.appendChild(output);
const loadButton = document.createElement('button');
loadButton.textContent = 'Bibliothèque de chargement';
document.body.appendChild(loadButton);
const result = document.createElement('div');
result.style.marginTop = '10px';
output.appendChild(result);
fromEvent(loadButton, 'click').pipe(
mergeMap(() => {
result.textContent = 'Chargement...';
// CDN1Chargement (simulé) de
const cdn1$ = timer(Math.random() * 3000).pipe(
map(() => ({ source: 'CDN1 (US)', data: 'library.js' }))
);
// ✅ Pipeable OperatorEdition - CDN1dans le principal et d'autresCDNcomme concurrents.
return cdn1$.pipe(
raceWith(
// CDN2Chargement (simulé) de
timer(Math.random() * 3000).pipe(
map(() => ({ source: 'CDN2 (EU)', data: 'library.js' }))
),
// CDN3Chargement (simulé) de
timer(Math.random() * 3000).pipe(
map(() => ({ source: 'CDN3 (Asia)', data: 'library.js' }))
)
)
);
})
).subscribe(response => {
result.innerHTML = `
<strong>✅ Chargement effectué</strong><br>
Acquis à partir de: ${response.source}<br>
Dossier: ${response.data}
`;
result.style.color = 'green';
});Résumé.
race: idéal si vous voulez simplement adopter le plus rapide parmi plusieurs flux.- raceWith : idéal si vous voulez implémenter des timeouts et des fallbacks pendant que vous transformez et traitez le flux principal.
⚠️ Notes.
Exemple de mise en œuvre d'un délai d'attente
Implémentation de la gestion du timeout en utilisant raceWith.
import { of, timer, throwError } from 'rxjs';
import { raceWith, delay, mergeMap } from 'rxjs';
// Processus chronophage (3secondes)
const slowRequest$ = of('Acquisition de données réussie').pipe(delay(3000));
// Délai d'attente (en2secondes)
const timeout$ = timer(2000).pipe(
mergeMap(() => throwError(() => new Error('Délai d'attente (s)')))
);
slowRequest$
.pipe(raceWith(timeout$))
.subscribe({
next: console.log,
error: err => console.error(err.message)
});
// Sortie: Délai d'attente (s)Tous les flux sont abonnés à
raceWith abonne tous les Observable jusqu'à ce qu'un gagnant soit désigné. Une fois le gagnant désigné, l'Observable perdant est automatiquement désabonné.
import { timer } from 'rxjs';
import { raceWith, tap, map } from 'rxjs';
const slow$ = timer(3000).pipe(
tap(() => console.log('slow$ Le tir')),
map(() => 'slow')
);
const fast$ = timer(1000).pipe(
tap(() => console.log('fast$ Le tir')),
map(() => 'fast')
);
slow$.pipe(raceWith(fast$)).subscribe(console.log);
// Sortie:
// fast$ Le tir
// fast
// (slow$est1Désabonné à la seconde,3n'est pas tiré à la fin de la deuxième période.)Pour les Observables synchrones.
Si tout est émis de manière synchrone, le premier enregistré est le gagnant.
import { of } from 'rxjs';
import { raceWith } from 'rxjs';
of('A').pipe(
raceWith(of('B'), of('C'))
).subscribe(console.log);
// Sortie: A (parce qu'il a d'abord été abonné à)