subscribeOn - Timing d'abonnement
L'opérateur subscribeOn contrôle le timing de démarrage de l'abonnement et le contexte d'exécution en utilisant un planificateur spécifié. Il affecte le timing d'exécution du flux entier.
🔰 Syntaxe et comportement de base
Spécifiez un planificateur pour rendre le démarrage de l'abonnement asynchrone.
import { of, asyncScheduler } from 'rxjs';
import { subscribeOn } from 'rxjs';
console.log('Début');
of(1, 2, 3)
.pipe(
subscribeOn(asyncScheduler)
)
.subscribe(v => console.log('Valeur :', v));
console.log('Fin');
// Sortie :
// Début
// Fin
// Valeur : 1
// Valeur : 2
// Valeur : 3Puisque le démarrage de l'abonnement est rendu asynchrone, l'appel subscribe() retourne immédiatement.
🌐 Documentation officielle RxJS - subscribeOn
💡 Cas d'utilisation typiques
- Initialisation lourde asynchrone : Retarder le début du chargement des données
- Prévention du gel de l'UI : Rendre le démarrage de l'abonnement asynchrone pour maintenir la réactivité
- Priorisation du traitement : Contrôle du timing de démarrage de plusieurs flux
- Contrôle du timing dans les tests : Utilisation de TestScheduler pour le contrôle
🧪 Exemple de code pratique 1 : Initialisation lourde asynchrone
Exemple de démarrage asynchrone du chargement de données ou de l'initialisation.
import { Observable, asyncScheduler } from 'rxjs';
import { subscribeOn } from 'rxjs';
// Création de l'interface utilisateur
const container = document.createElement('div');
document.body.appendChild(container);
const title = document.createElement('h3');
title.textContent = 'subscribeOn - Initialisation lourde';
container.appendChild(title);
const output = document.createElement('div');
output.style.border = '1px solid #ccc';
output.style.padding = '10px';
container.appendChild(output);
function addLog(message: string, color: string = '#e3f2fd') {
const logItem = document.createElement('div');
logItem.style.padding = '5px';
logItem.style.marginBottom = '3px';
logItem.style.backgroundColor = color;
const now = new Date();
const timestamp = now.toLocaleTimeString('fr-FR', { hour12: false }) +
'.' + now.getMilliseconds().toString().padStart(3, '0');
logItem.textContent = `[${timestamp}] ${message}`;
output.appendChild(logItem);
}
// Simulation d'un traitement d'initialisation lourd
const heavyInit$ = new Observable<string>(subscriber => {
addLog('Début du chargement des données...', '#fff9c4');
// Simulation d'un traitement lourd
let sum = 0;
for (let i = 0; i < 10000000; i++) {
sum += i;
}
addLog('Chargement des données terminé', '#c8e6c9');
subscriber.next(`Résultat : ${sum}`);
subscriber.complete();
});
addLog('Démarrage de l\'abonnement (UI opérationnelle)', '#e3f2fd');
heavyInit$
.pipe(
subscribeOn(asyncScheduler) // Rendre le démarrage de l'abonnement asynchrone
)
.subscribe({
next: result => addLog(`Reçu : ${result}`, '#c8e6c9'),
complete: () => addLog('Terminé', '#e3f2fd')
});
addLog('Après la demande d\'abonnement (exécution continue immédiatement)', '#e3f2fd');- Le démarrage de l'abonnement est rendu asynchrone, l'UI répond immédiatement
- Le traitement lourd s'exécute de manière asynchrone
- Le thread principal n'est pas bloqué
🧪 Exemple de code pratique 2 : Contrôle de priorité pour plusieurs flux
Exemple de contrôle du timing de démarrage de plusieurs flux.
import { interval, asyncScheduler, asapScheduler } from 'rxjs';
import { subscribeOn, take, tap } from 'rxjs';
// Création de l'interface utilisateur
const container2 = document.createElement('div');
container2.style.marginTop = '20px';
document.body.appendChild(container2);
const title2 = document.createElement('h3');
title2.textContent = 'subscribeOn - Contrôle de priorité';
container2.appendChild(title2);
const output2 = document.createElement('div');
output2.style.border = '1px solid #ccc';
output2.style.padding = '10px';
output2.style.maxHeight = '200px';
output2.style.overflow = 'auto';
container2.appendChild(output2);
function addLog2(message: string, color: string) {
const now = new Date();
const timestamp = now.toLocaleTimeString('fr-FR', { hour12: false }) +
'.' + now.getMilliseconds().toString().padStart(3, '0');
const logItem = document.createElement('div');
logItem.style.padding = '3px';
logItem.style.marginBottom = '2px';
logItem.style.backgroundColor = color;
logItem.style.fontSize = '12px';
logItem.textContent = `[${timestamp}] ${message}`;
output2.appendChild(logItem);
}
addLog2('Début', '#e3f2fd');
// Tâche haute priorité (asapScheduler)
interval(500)
.pipe(
take(3),
subscribeOn(asapScheduler),
tap(v => addLog2(`Haute priorité : ${v}`, '#c8e6c9'))
)
.subscribe();
// Tâche priorité normale (asyncScheduler)
interval(500)
.pipe(
take(3),
subscribeOn(asyncScheduler),
tap(v => addLog2(`Priorité normale : ${v}`, '#fff9c4'))
)
.subscribe();
addLog2('Demandes d\'abonnement terminées', '#e3f2fd');- Contrôle de priorité avec différents planificateurs
asapSchedulerdémarre l'exécution avantasyncScheduler
🆚 Différence avec observeOn
import { of, asyncScheduler } from 'rxjs';
import { observeOn, subscribeOn, tap } from 'rxjs';
// Exemple avec observeOn
console.log('=== observeOn ===');
console.log('1: Début');
of(1, 2, 3)
.pipe(
tap(() => console.log('2: tap (synchrone)')),
observeOn(asyncScheduler),
tap(() => console.log('4: tap (asynchrone)'))
)
.subscribe(() => console.log('5: subscribe'));
console.log('3: Fin');
// Exemple avec subscribeOn
console.log('\n=== subscribeOn ===');
console.log('1: Début');
of(1, 2, 3)
.pipe(
tap(() => console.log('3: tap (asynchrone)')),
subscribeOn(asyncScheduler)
)
.subscribe(() => console.log('4: subscribe'));
console.log('2: Fin');Principales différences :
| Élément | observeOn | subscribeOn |
|---|---|---|
| Portée | Opérations suivantes uniquement | Flux entier |
| Cible du contrôle | Timing d'émission des valeurs | Timing de démarrage de l'abonnement |
| Position de placement | Important (le comportement change selon la position) | Même effet quelle que soit la position |
| Utilisation multiple | Le dernier s'applique | Le premier s'applique |
NOTE
Pour plus de détails sur observeOn, voir observeOn.
⚠️ Notes importantes
1. La position de placement n'a pas d'importance
subscribeOn a le même effet quel que soit l'endroit où il est placé dans le pipeline.
import { of, asyncScheduler } from 'rxjs';
import { subscribeOn, map } from 'rxjs';
// Pattern 1 : Au début
of(1, 2, 3)
.pipe(
subscribeOn(asyncScheduler),
map(x => x * 2)
)
.subscribe();
// Pattern 2 : À la fin
of(1, 2, 3)
.pipe(
map(x => x * 2),
subscribeOn(asyncScheduler)
)
.subscribe();
// Les deux ont le même comportement2. En cas d'utilisation multiple, le premier est appliqué
import { of, asyncScheduler, asapScheduler } from 'rxjs';
import { subscribeOn } from 'rxjs';
of(1, 2, 3)
.pipe(
subscribeOn(asyncScheduler), // Celui-ci est utilisé
subscribeOn(asapScheduler) // Celui-ci est ignoré
)
.subscribe();Le planificateur du premier subscribeOn (asyncScheduler) est utilisé.
3. Pas d'effet sur certains Observables
subscribeOn n'a pas d'effet sur les Observables qui ont leur propre planificateur comme interval ou timer.
import { interval, asyncScheduler } from 'rxjs';
import { subscribeOn } from 'rxjs';
// ❌ subscribeOn n'a pas d'effet
interval(1000)
.pipe(
subscribeOn(asyncScheduler) // interval utilise son propre planificateur
)
.subscribe();
// ✅ Spécifier le planificateur dans l'argument d'interval
interval(1000, asyncScheduler)
.subscribe();Exemple de combinaison pratique
import { of, asyncScheduler, animationFrameScheduler } from 'rxjs';
import { subscribeOn, observeOn, map, tap } from 'rxjs';
console.log('Début');
of(1, 2, 3)
.pipe(
tap(() => console.log('Tap 1 (asynchrone)')),
subscribeOn(asyncScheduler), // Rendre le démarrage de l'abonnement asynchrone
map(x => x * 2),
observeOn(animationFrameScheduler), // Synchroniser l'émission des valeurs avec l'animation frame
tap(() => console.log('Tap 2 (animation frame)'))
)
.subscribe(v => console.log('Valeur :', v));
console.log('Fin');
// Ordre d'exécution :
// Début
// Fin
// Tap 1 (asynchrone)
// Tap 1 (asynchrone)
// Tap 1 (asynchrone)
// Tap 2 (animation frame)
// Valeur : 2
// ... (suite)Guide d'utilisation
Cas 1 : Vous voulez retarder le démarrage de l'abonnement
// → Utiliser subscribeOn
of(données)
.pipe(subscribeOn(asyncScheduler))
.subscribe();Cas 2 : Vous voulez rendre seulement certaines opérations asynchrones
// → Utiliser observeOn
of(données)
.pipe(
map(traitementLourd),
observeOn(asyncScheduler), // Seulement après le traitement lourd
map(traitementLéger)
)
.subscribe();Cas 3 : Tout rendre asynchrone + contrôle supplémentaire de parties
// → Utiliser subscribeOn + observeOn ensemble
of(données)
.pipe(
subscribeOn(asyncScheduler), // Tout rendre asynchrone
map(traitement1),
observeOn(animationFrameScheduler), // Changer pour l'animation
map(traitement2)
)
.subscribe();📚 Opérateurs associés
📖 Documentation associée
- Contrôle du traitement asynchrone - Bases des planificateurs
- Types et utilisation des planificateurs - Détails de chaque planificateur
✅ Résumé
L'opérateur subscribeOn contrôle le timing de démarrage de l'abonnement et le contexte d'exécution.
- ✅ Rend le démarrage de l'abonnement du flux entier asynchrone
- ✅ Efficace pour l'initialisation lourde asynchrone
- ✅ Utile pour prévenir le gel de l'UI
- ✅ La position de placement n'a pas d'importance
- ⚠️ En cas d'utilisation multiple, le premier est appliqué
- ⚠️ Pas d'effet sur certains Observables
- ⚠️ Objectif différent de
observeOn