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,177