Veelvoorkomende debugscenario's
Uitleg over typische problemen die je tegenkomt bij RxJS-ontwikkeling en hun oplossingen, met concrete codevoorbeelden.
Scenario 1: Waarden stromen niet
- Symptoom: Ondanks het abonneren met
subscribe, worden er geen waarden geproduceerd
Oorzaak 1: Vergeten te subscriben op Cold Observable
ts
import { interval } from 'rxjs';
import { map } from 'rxjs';
// ❌ Wordt niet uitgevoerd zonder subscription
const numbers$ = interval(1000).pipe(
map(x => {
console.log('Deze regel wordt niet uitgevoerd');
return x * 2;
})
);
// ✅ Wordt uitgevoerd door te subscriben
numbers$.subscribe(value => console.log('Waarde:', value));Oorzaak 2: Voltooide Subject
ts
import { Subject } from 'rxjs';
const subject = new Subject<number>();
subject.complete(); // Voltooid
// ❌ Na voltooiing worden geen waarden meer ontvangen
subject.subscribe(value => console.log('Deze regel wordt niet uitgevoerd'));
// ✅ Subscribe voor voltooiing
const subject2 = new Subject<number>();
subject2.subscribe(value => console.log('Waarde:', value));
subject2.next(1); // Waarde: 1
subject2.complete();Oorzaak 3: Filteren met verkeerde voorwaarden
ts
import { of } from 'rxjs';
import { filter, tap } from 'rxjs';
of(1, 2, 3, 4, 5)
.pipe(
tap(value => console.log('Voor filter:', value)),
filter(x => x > 10), // Alle waarden worden uitgefilterd
tap(value => console.log('Na filter:', value)) // Deze regel wordt niet uitgevoerd
)
.subscribe({
next: value => console.log('Eindwaarde:', value),
complete: () => console.log('Voltooid (geen waarden)')
});
// Output:
// Voor filter: 1
// Voor filter: 2
// Voor filter: 3
// Voor filter: 4
// Voor filter: 5
// Voltooid (geen waarden)Debugtechniek
ts
import { of, EMPTY } from 'rxjs';
import { filter, tap, defaultIfEmpty } from 'rxjs';
of(1, 2, 3, 4, 5)
.pipe(
tap(value => console.log('🔵 Input:', value)),
filter(x => x > 10),
tap(value => console.log('🟢 Gepasseerd filter:', value)),
defaultIfEmpty('Geen waarden') // Standaardwaarde als er geen waarden zijn
)
.subscribe(value => console.log('✅ Output:', value));
// Output:
// 🔵 Input: 1
// 🔵 Input: 2
// 🔵 Input: 3
// 🔵 Input: 4
// 🔵 Input: 5
// ✅ Output: Geen waardenScenario 2: Onverwachte waarden worden geproduceerd
- Symptoom: Er worden andere waarden geproduceerd dan verwacht
Oorzaak 1: Verkeerde operatorvolgorde
ts
import { of } from 'rxjs';
import { map, filter } from 'rxjs';
// ❌ Onverwacht resultaat
of(1, 2, 3, 4, 5)
.pipe(
map(x => x * 2), // 2, 4, 6, 8, 10
filter(x => x < 5) // Alleen 2, 4 passeren
)
.subscribe(value => console.log('Resultaat:', value));
// Output: 2, 4
// ✅ Correcte volgorde
of(1, 2, 3, 4, 5)
.pipe(
filter(x => x < 5), // Alleen 1, 2, 3, 4 passeren
map(x => x * 2) // 2, 4, 6, 8
)
.subscribe(value => console.log('Resultaat:', value));
// Output: 2, 4, 6, 8Oorzaak 2: Onbedoelde wijzigingen door gedeelde referenties
ts
import { of } from 'rxjs';
import { map } from 'rxjs';
interface User {
id: number;
name: string;
}
const user: User = { id: 1, name: 'Alice' };
of(user)
.pipe(
// ❌ Direct wijzigen van het originele object
map(u => {
u.name = 'Bob'; // Origineel object wordt gewijzigd
return u;
})
)
.subscribe(value => console.log('Na wijziging:', value));
console.log('Origineel object:', user); // { id: 1, name: 'Bob' }
// ✅ Nieuw object creëren
of(user)
.pipe(
map(u => ({ ...u, name: 'Charlie' })) // Nieuw object met spread-syntax
)
.subscribe(value => console.log('Na wijziging:', value));
console.log('Origineel object:', user); // { id: 1, name: 'Alice' } (niet gewijzigd)Oorzaak 3: Timing van asynchrone verwerking
ts
import { of, delay } from 'rxjs';
import { mergeMap, tap } from 'rxjs';
// ❌ Wacht niet op voltooiing van asynchrone verwerking
of(1, 2, 3)
.pipe(
tap(value => console.log('Start:', value)),
mergeMap(value =>
of(value * 2).pipe(
delay(100 - value * 10) // Grotere waarden voltooien sneller
)
)
)
.subscribe(value => console.log('Voltooid:', value));
// Output:
// Start: 1
// Start: 2
// Start: 3
// Voltooid: 3 ← Kortste vertraging
// Voltooid: 2
// Voltooid: 1 ← Langste vertraging
// ✅ Volgorde garanderen
import { concatMap } from 'rxjs';
of(1, 2, 3)
.pipe(
tap(value => console.log('Start:', value)),
concatMap(value => // mergeMap → concatMap
of(value * 2).pipe(delay(100 - value * 10))
)
)
.subscribe(value => console.log('Voltooid:', value));
// Output:
// Start: 1
// Voltooid: 1
// Start: 2
// Voltooid: 2
// Start: 3
// Voltooid: 3Scenario 3: Subscription wordt niet voltooid (oneindige stream)
- Symptoom:
completewordt niet aangeroepen en de stream eindigt niet
ts
import { interval } from 'rxjs';
import { tap } from 'rxjs';
// ❌ interval blijft oneindig waarden produceren
interval(1000)
.pipe(
tap(value => console.log('Waarde:', value))
)
.subscribe({
complete: () => console.log('Deze regel wordt niet uitgevoerd')
});
// ✅ Expliciet voltooien met take
import { take } from 'rxjs';
interval(1000)
.pipe(
take(5), // Voltooid na 5 waarden
tap(value => console.log('Waarde:', value))
)
.subscribe({
complete: () => console.log('Voltooid')
});Debugtechniek
ts
import { interval, timer } from 'rxjs';
import { tap, takeUntil } from 'rxjs';
// Timeout instellen voor debugging
const stop$ = timer(5000); // Voltooid na 5 seconden
interval(1000)
.pipe(
takeUntil(stop$),
tap({
next: value => console.log('Waarde:', value),
complete: () => console.log('Gestopt door timeout')
})
)
.subscribe();Scenario 4: Geheugenlek (vergeten unsubscribe)
- Symptoom: Applicatie wordt geleidelijk langzamer
Oorzaak: Subscriptions niet opzeggen wanneer niet meer nodig
ts
import { interval } from 'rxjs';
class UserComponent {
private subscription: any;
ngOnInit() {
// ❌ Vergeten te unsubscribe
interval(1000).subscribe(value => {
console.log('Waarde:', value); // Blijft uitvoeren na vernietiging component
});
}
ngOnDestroy() {
// Geen unsubscribe
}
}
// ✅ Subscriptions juist beheren
class UserComponentFixed {
private subscription: any;
ngOnInit() {
this.subscription = interval(1000).subscribe(value => {
console.log('Waarde:', value);
});
}
ngOnDestroy() {
// Unsubscribe bij vernietiging component
if (this.subscription) {
this.subscription.unsubscribe();
}
}
}Aanbevolen patroon: gebruik takeUntil
ts
import { interval, Subject } from 'rxjs';
import { takeUntil } from 'rxjs';
class UserComponentBest {
private destroy$ = new Subject<void>();
ngOnInit() {
// ✅ Automatische unsubscribe met takeUntil
interval(1000)
.pipe(
takeUntil(this.destroy$)
)
.subscribe(value => console.log('Waarde:', value));
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}Detecteren van geheugenlekken
ts
import { interval } from 'rxjs';
import { tap } from 'rxjs';
let subscriptionCount = 0;
const trackSubscriptions = <T>() =>
tap<T>({
subscribe: () => {
subscriptionCount++;
console.log('📈 Aantal subscriptions:', subscriptionCount);
},
unsubscribe: () => {
subscriptionCount--;
console.log('📉 Aantal subscriptions:', subscriptionCount);
}
});
// Gebruiksvoorbeeld
const stream$ = interval(1000).pipe(
trackSubscriptions()
);
const sub1 = stream$.subscribe();
// Output: 📈 Aantal subscriptions: 1
const sub2 = stream$.subscribe();
// Output: 📈 Aantal subscriptions: 2
setTimeout(() => {
sub1.unsubscribe();
// Output: 📉 Aantal subscriptions: 1
}, 3000);Scenario 5: Fouten worden niet opgemerkt
- Symptoom: Fouten treden op maar worden genegeerd en niet weergegeven
ts
import { of, throwError } from 'rxjs';
import { mergeMap, catchError } from 'rxjs';
// ❌ Fouten worden onderdrukt zonder foutafhandeling
of(1, 2, 3)
.pipe(
mergeMap(value => {
if (value === 2) {
return throwError(() => new Error('Fout'));
}
return of(value);
})
)
.subscribe(); // Geen error handler
// ✅ Passende foutafhandeling
of(1, 2, 3)
.pipe(
mergeMap(value => {
if (value === 2) {
return throwError(() => new Error('Fout'));
}
return of(value);
}),
catchError(error => {
console.error('🔴 Fout gevangen:', error.message);
return of(-1); // Fallback-waarde
})
)
.subscribe({
next: value => console.log('Waarde:', value),
error: error => console.error('🔴 Fout in subscription:', error)
});
// Output:
// Waarde: 1
// 🔴 Fout gevangen: Fout
// Waarde: -1Globale fouthandler instellen
ts
import { Observable } from 'rxjs';
// Vang alle onafgehandelde fouten op
const originalCreate = Observable.create;
Observable.create = function(subscribe: any) {
return originalCreate.call(this, (observer: any) => {
try {
return subscribe(observer);
} catch (error) {
console.error('🔴 Onafgehandelde fout:', error);
observer.error(error);
}
});
};Scenario 6: Retry-pogingen willen bijhouden
- Symptoom: De
retry-operator wordt gebruikt, maar je weet niet hoe vaak er wordt geretried
Bij automatische retry's na fouten wordt debuggen en logging gemakkelijker door bij te houden hoeveel keer er daadwerkelijk wordt geretried.
Basis retry-debugging
ts
import { throwError, of, timer } from 'rxjs';
import { retryWhen, mergeMap, tap } from 'rxjs';
throwError(() => new Error('Tijdelijke fout'))
.pipe(
retryWhen((errors) =>
errors.pipe(
mergeMap((error, index) => {
const retryCount = index + 1;
console.log(`🔄 Retry-poging ${retryCount}`);
if (retryCount > 2) {
console.log('❌ Maximum aantal retry\'s bereikt');
throw error;
}
return timer(1000);
})
)
)
)
.subscribe({
next: value => console.log('✅ Geslaagd:', value),
error: error => console.log('🔴 Definitieve fout:', error.message)
});
// Output:
// 🔄 Retry-poging 1
// 🔄 Retry-poging 2
// 🔄 Retry-poging 3
// ❌ Maximum aantal retry's bereikt
// 🔴 Definitieve fout: Tijdelijke foutTIP
Voor meer gedetailleerde implementatiepatronen van retry-debugging, zie het gedeelte "Retry debugging" in retry en catchError.
- Basis tracking met tap error callback
- Gedetailleerde logging met retryWhen
- Exponentiële backoff met logging
- RxJS 7.4+ retry-configuratieobject
Samenvatting
Oplossingen voor veelvoorkomende debugscenario's
- ✅ Waarden stromen niet → Controleer vergeten subscription, filtervoorwaarden
- ✅ Onverwachte waarden → Let op operatorvolgorde, delen van referenties
- ✅ Subscription wordt niet voltooid → Gebruik
takeoftakeUntilvoor oneindige streams - ✅ Geheugenlek → Automatische unsubscribe met
takeUntil-patroon - ✅ Gemiste fouten → Implementeer passende foutafhandeling
- ✅ Retry-tracking → Log met
retryWhenof configuratieobject
Gerelateerde pagina's
- Basis debugstrategieën - Gebruik van tap-operator en ontwikkelaarstools
- Aangepaste debugtools - Named streams, debug-operators
- Prestatiedebuggen - Subscription-monitoring, geheugengebruik controleren
- Foutafhandeling - Strategieën voor foutverwerking