by Gerard Sans | @gerardsans
Bending time
with Schedulers and RxJS 6
Bending time
with Schedulers and RxJS 6
Google Developer Expert
Google Developer Expert
International Speaker
Spoken at 87 events in 25 countries
Blogger
Blogger
Community Leader
900
1.5K
Trainer
Master of Ceremonies
Master of Ceremonies
Woot!? Event Loop?
Macro & microtasks Quiz
const log = console.log;
const macro = v => setTimeout(() => log(v));
const micro = v => Promise.resolve().then(() => log(v));
log(1);
macro(2);
micro(3);
log(4);
a) 1 2 3 4
b) 1 4 2 3
c) 1 4 3 2
Event Loop
waits microtasks
JavaScript: call me maybe?
setTimeout Quiz
setTimeout(() => log(1));
setTimeout(() => log(2), 0);
log(3)
// a) 1 2 3
// b) 3 2 1
// c) 3 1 2
YAY!
setTimeout Quiz
setTimeout(() => log(1), 1000);
setTimeout(() => log(2), 2000);
setTimeout(() => log(3), 3000);
// a) 1 2 3 (wait aprox. 1s)
// b) 1 2 3 (wait aprox. 3s)
// c) 1 2 3 (wait aprox. 6s)
setInterval(task, 50)
Time
50ms
task 1
task 2
task 3
task < delay
setInterval(task, 50)
Time
50ms
task 1
task 2
task 3
task > delay
task 1
task 2
task 3
Reality
Ideal
Delay is only
time to enter
Event Loop
RxJS help me!
audit auditTime buffer bufferCount bufferTime bufferToggle bufferWhen catchError combineAll combineLatest concat concatAll concatMap concatMapTo count debounce debounceTime defaultIfEmpty delay delayWhen dematerialize distinct distinctUntilChanged distinctUntilKeyChanged elementAt endWith every exhaust exhaustMap expand filter finalize find findIndex first groupBy ignoreElements isEmpty last map mapTo materialize max merge mergeAll mergeMap mergeMap as flatMap mergeMapTo mergeScan min multicast observeOn onErrorResumeNext pairwise partition pluck publish publishBehavior publishLast publishReplay race reduce repeat repeatWhen retry retryWhen refCount sample sampleTime scan sequenceEqual share shareReplay single skip skipLast skipUntil skipWhile startWith subscribeOn switchAll switchMap switchMapTo take takeLast takeUntil takeWhile tap throttle throttleTime throwIfEmpty timeInterval timeout timeoutWith timestamp toArray window windowCount windowTime windowToggle windowWhen withLatestFrom zip zipAll
103 operators
Synchronous Operators
Synchronous by default
- Examples: of, from, range
- Default Scheduler: queue
Asynchronous Operators
Asynchronous by default
- Examples: timer, interval
- Scheduler: async
- Primitive: setInterval
Changing default Scheduler
of(1).subscribe(v => l(v));
l(2);
// 1 2
import { asyncScheduler } from 'rxjs';
of(1, asyncScheduler).subscribe(v => l(v));
l(2);
// 2 1
Least Concurrency Principle
What's new in RxJS 6!
RxJS v6 Goodies
-
Friendly imports
- rxjs, rxjs/operators
-
EcmaScript spec compliant
- Pipeable operators
- Avoid reserved keywords
-
Simplified API
- Result Selectors removed
Operator Renames
do throw switch finally
tap throwError switchAll finalize
Listing odd numbers
import { interval } from 'rxjs';
import { filter } from 'rxjs/operators';
const isOddNumber = (x: number) => x%2!==0;
interval(1000).pipe(
filter(isOddNumber)
);
// --0--1--2--3--4--5-- interval
// -----1-----3-----5-- filter
Error Handling
import { throwError } from 'rxjs';
import { catchError, finalize } from 'rxjs/operators';
throwError("💩").subscribe({
error: e => l(`Error: ${e}`)
})
throwError("💩").pipe(
catchError(e => of("No worries. Sorted!😃")),
finalize(() => { /* code */ }),
).subscribe(v => l(v));
Scheduler operators
1
1
1
Timeline
Emitted values
complete
next
next
next
Observable
Sync Implementation
// (111|)
let a$ = Observable.create(observer => {
observer.next(1);
observer.next(1);
observer.next(1);
observer.complete();
});
let subscription = a$.subscribe({
next: v => log(v),
complete: () => log('|')
});
Async Implementation
// --1--1--1|
let a$ = Observable.create(observer => {
setTimeout(() => observer.next(1));
setTimeout(() => observer.next(1));
setTimeout(() => observer.next(1));
setTimeout(() => observer.complete());
});
let subscription = a$.subscribe({
next: v => log(v),
complete: () => log('|')
});
subscribeOn
1
1
1
complete
next
next
next
1
1
1
map(v => v)
subscribeOn(asyncScheduler)
.subscribe()
subscribeOn
- Changes Source Execution
- Only used once
observeOn
1
1
1
complete
next
next
next
1
1
1
map(v => v)
observeOn(asyncScheduler)
.subscribe()
observeOn
-
Changes Notifications Execution
- Next, Error, Complete
- Can be used before each operator
subscribeOn Quiz
of(1).pipe(subscribeOn(asyncScheduler))
.subscribe({
next: x => l(x),
complete: () => l('3')
});
l('2');
// a) 1 2 3
// b) 2 1 3
// c) 1 3 2
Queue Scheduler
Overview
- Executes Synchronously
- Tasks execute in order
- Waits until current task ends before executing next one
- Performant (precedes Event Loop)
QueueScheduler Example
import { queueScheduler } from 'rxjs';
queueScheduler.schedule(() => log(1));
log(2);
queueScheduler.schedule(() => log(3));
// 1 2 3
queueScheduler.schedule(() => {
queueScheduler.schedule(() => log(1));
log(2);
queueScheduler.schedule(() => log(3));
});
// 2 1 3
Asap Scheduler
Overview
- Executes Asynchronously (micro)
- Tasks execute before next tick
- Relays on Promises
- Performant (precedes Event Loop)
AsapScheduler Example
import { asapScheduler } from 'rxjs';
import { queueScheduler } from 'rxjs';
setTimeout(() => log(1));
asapScheduler.schedule(() => log(2));
queueScheduler.schedule(() => log(3));
// 3 2 1
Async Scheduler
Overview
- Executes Asynchronously (macro)
- Relays on setInterval
- Less performant (uses Event Loop)
AsyncScheduler Example
import { asyncScheduler } from 'rxjs';
import { queueScheduler } from 'rxjs';
asapScheduler.schedule(() => log(2));
asyncScheduler.schedule(() => log(1));
queueScheduler.schedule(() => log(3));
// 3 2 1
Cancelling tasks
import { asyncScheduler } from 'rxjs';
const s = asyncScheduler;
const DELAY = 0;
let subscription;
subscription = s.schedule(v => log(v), DELAY, 1);
s.schedule(v => log(v), DELAY, 2);
log(3);
subscription.unsubscribe();
// 3
// 2
Internal Time
import { asyncScheduler } from 'rxjs';
const s = asyncScheduler;
const DELAY = 2000;
const start = Date.now();
s.schedule(v => log(v), DELAY, 1);
s.schedule(v => log(v), DELAY, 2);
s.schedule(() => log(`${s.now()-start}ms`), DELAY);
log(3);
// 3
// 1
// 2
// 2008ms
Animations Scheduler
Overview
- Executes Asynchronously
- Relays on requestAnimationFrame
- Adapts to Device Frame Rate
- Slows down when is not active
- Balances CPU/GPU load
Animations
FRAMES
Frame Rates
60 FPS
Time
16.66ms
16.66ms
16.66ms
1000/60ms
paint
frame
paint
frame
paint
frame
paint
frame
paint
frame
Stutter or Losing Frames!
60 FPS
Time
16.66ms
16.66ms
16.66ms
using setInterval
const token;
const paintFrame = () => {
// animation code
token = setInterval(paintFrame, 1000/60);
}
paintFrame();
setTimeout(() => clearInterval(token), 2000);
Issues
- Ignores Device Frame Rate
- Runs all the time (batteries enemy)
- Ignores current CPU/GPU load
requestAnimationFrame
60 FPS
Time
16.66ms
16.66ms
16.66ms
1000/60ms
paint
frame
paint
frame
paint
frame
requestAnimationFrame
const token;
const paintFrame = (timestamp) => {
// animation code
token = requestAnimationFrame(paintFrame)
}
requestAnimationFrame(paintFrame);
setTimeout(() => cancelAnimationFrame(token), 2000);
AnimationFrameScheduler
import { animationFrameScheduler } from 'rxjs';
const s = animationFrameScheduler;
const DELAY = 0;
const state = { angle: 0 }
const div = document.querySelector('.circle');
const work = state => {
let {angle} = state;
div.style.transform = `rotate(${angle}deg)`;
s.schedule(work, DELAY, { angle: ++angle%360 });
}
s.schedule(work, DELAY, state);
Wrapping up
What is a Scheduler?
- Data Structure
- Execution context
- Virtual clock
Schedulers Overview
Type | Execution | Primitives |
---|---|---|
queueScheduler | Sync | scheduler.schedule(task, delay) scheduler.flush() |
asapScheduler | Async (micro) | Promise.resolve().then(() => task) |
asyncScheduler | Async (macro) | id = setInterval(task, delay) clearInterval(id) |
animationFrameScheduler | Async | id = requestAnimationFrame(task) cancelAnimationFrame(id) |
More
Thanks
Bending time with Schedulers and RxJS 6
By Gerard Sans
Bending time with Schedulers and RxJS 6
Observables have been very popular because of their many qualities: asynchronous processing, composition, performance, powerful operators. But usually there's a less covered feature that lies beneath. That is: Schedulers. In this talk we are going to cover Schedulers in depth, going from the basic APIs to more obscure features to bend time to our will!
- 4,086