RxJS Essential
Andrew Zhang
2021.8.12
Category
- Observable
- Cold vs Hot
- Subject
- Operators
- Memory Leak
Observable
Observable
- of
- range
- interval
- fromEvent
- fromPromise
Publisher
Observer
notify
subscribe
Obserable = Publisher + Iterator
import { Observable } from 'rxjs';
const source$ = new Observable(observer => {
observer.next(1);
observer.next(2);
observer.next(3);
});
source$.subscribe(item => {
console.log(item);
});
State
Normal
Error
Complete
next
complete
error
1
2
1
2
1
2
1
2
3
import { Observable } from 'rxjs';
new Observable(observer => {
let number = 1;
setInterval(() => {
observer.next(number++);
}, 1000);
});
new Observable(observer => {
let number = 1;
setInterval(() => {
if (number > 2) {
observer.complete();
}
observer.next(number++);
}, 1000);
});
new Observable(observer => {
let number = 1;
setInterval(() => {
if (number > 2) {
observer.error();
}
observer.next(number++);
}, 1000);
});
Cold vs Hot
Cold Observable
Observer
Observer
Observer
Observable
Observable
Observable
Unicast
1
2
3
1
2
3
Observer A
Observer B
interval, range, of
Hot Observable
Observer
Observer
Observer
Observable
Multicast
1
2
3
2
3
Observer A
Observer B
fromPromise, fromEvent
Cold 2 Hot
import { Observable } from "rxjs";
const cold = new Observable(observer => {
observer.next(Math.random());
});
cold.subscribe(a => console.log(a));
cold.subscribe(b => console.log(b));
const producer = Math.random();
const hot = new Observable(observer => {
observer.next(producer);
})
hot.subscribe(a => console.log(a))
hot.subscribe(b => console.log(b))
Hot
Cold
Lazy
Lazier
Subject
Subject
import { Subject, interval } from 'rxjs';
const tick$ = interval(1000)
const subject = new Subject();
tick$.subscribe(subject);
const subscription1 = subject.subscribe(
item => console.log(item),
);
setTimeout(() => {
const subscription2 = subject.subscribe(
item => console.log(item),
)
}, 1500);
import { interval } from 'rxjs';
const tick$ = interval(1000);
const subscription1 = tick$.subscribe(
item => console.log(item),
);
setTimeout(() => {
const subscription2 = tick$.subscribe(
item => console.log(item),
);
}, 1500);
Subject = Observable + Observer
0
1
2
0
1
2
0
1
2
1
2
Subject
Cold
Observer
Observer
Observer
Cold
hot
BehaviorSubject
import { BehaviorSubject } from 'rxjs';
const subject$ = new BehaviorSubject(0);
const subscription1 = subject$.subscribe(
item => console.log(item),
);
subject$.next(1);
subject$.next(2);
const subscription2 = subject.subscribe(
item => console.log(item),
);
subject$.next(3);
0, 1, 2, 3
2, 3
* 4
* 2
BehaviorSubject = Subject + shareReplay(1)
cache
Operators
Basic Operators
import { interval } from 'rxjs';
import {
map,
filter,
take,
scan,
} from 'rxjs/operators';
interval(1000).pipe(
map(n => Math.pow(n, 2)),
scan((acc, val) => acc + val),
filter(v => v > 10),
take(3),
)
import { interval } from 'rxjs';
import {
map,
filter,
take,
scan,
} from 'rxjs/operators';
interval(1000).pipe(
take(3),
map(n => Math.pow(n, 2)),
scan((acc, val) => acc + val),
filter(v => v > 10),
);
map
scan
fitler
take
Combination
source1$
source2$
source1$.pipe(concat(source2$))
merge(source1$, source2$)
source1$.pipe(zip(source2$))
source1$.pipe(withLatestFrom(source2$))
combineLatest(source1$, source2$)
forkJoin(source1$, source2$)
import { merge, combineLatest, forkJoin } from 'rxjs';
import { concat, zip, withLatestFrom } from 'rxjs/operators';
High-order Mapping
const source$ = interval(800);
source$.pipe(
take(3),
map(i =>
interval(500).pipe(take(3))
),
)
High-order Observable
concatMap
mergeMap
switchMap
exhaustMap
const source$ = interval(800);
source$.pipe(
take(3),
concatMap(i => project(i)),
)
Back Pressure
interval(200).pipe(
throttleTime(1000),
debounceTime(1000),
)
interval(200).pipe(
throttleTime(1000),
)
source
throttleTime
debounceTime
Error Handle
function throwOnUnluckyNumber(num) {
if (num === 4) {
throw new Error('unlucky number 4');
}
return num;
};
const source$ = interval(500).pipe(
map(i => throwOnUnluckyNumber(i))
);
const subscription1 = source$.pipe(
retry(1),
catchError(err => of(8)),
finalize(() => console.log('error or complete'))
)
// make it hot
const subject = new Subject();
source$.subscribe(subject);
const subscription2 = subject.pipe(
retry(1),
catchError(err => of(8)),
finalize(() => console.log('error or complete'))
)
Retry = Unsubscribe + Subscribe
Memory Leak
Unsubscribe
Timeline
No data stack
const source$ = new Subject();
let s = source$.subscribe(v => console.log(v));
source$.next("1");
s = null;
s = source$.subscribe(v => console.log(v));
source$.next("2");
s.unsubscribe();
Memory Leak
ngOnInit() {
this.dataSub = this.getData().subscribe();
this.cancelSub = fromEvent(
cancelBtn,
'click',
).subscribe();
this.rangeSub = fromEvent(
rangeSelector,
'change',
).subscribe();
}
ngOnDestroy() {
this.dataSub.unsubscribe();
this.cancelSub.unsubscribe();
this.rangeSub.unsubscribe();
}
Unsubscribe manually in time
Take, TakeUntil
Complete
$destroyer = new Subject();
ngOnInit() {
this.dataSub = this.getData().pipe(
takeUntil(this.destroyer$),
).subscribe();
this.cancelSub = fromEvent(
cancelBtn,
'click',
).pipe(
takeUntil(this.destroyer$),
).subscribe();
this.rangeSub = fromEvent(
rangeSelector,
'change',
).pipe(
takeUntil(this.destroyer$),
).subscribe();
}
ngOnDestroy() {
this.$destroyer.next();
this.$destroyer.complete();
this.$destroyer.unsubscribe();
}
import { Observable } from "rxjs";
import { switchMap, takeUntil } from "rxjs/operators";
declare const a: Observable<number>;
declare const b: Observable<number>;
declare const notifier: Observable<any>;
const c = a
.pipe(
takeUntil(notifier),
switchMap((_) => b)
)
.subscribe((value) => console.log(value));
TakeUntil Leak
RxJS Essential
By Junhao Zhang
RxJS Essential
- 29