RxJS 系列 – Scheduler

前言

大部分情况下, RxJS 都是用来处理异步执行的. 比如 Ajax, EventListener 等等.

但其实, 它也是可以同步执行的, 甚至 by default 它就是同步执行的 (下面会给例子). 

再加上 JS 的 Event Loop 本来就比较多变化, 所以 RxJS 就有了一个 Scheduler 的概念.

它用来控制执行的时机. 比如我们可以把同步执行, 改成异步执行.

 

参考

认识 RxJS 的 Scheduler

93. RxJS SubscribeOn Operator. Learn RxJS Utility SubscribeOn Operator - RxJS

94. RxJS ObserveOn Operator. Learn RxJS Utility ObserverOn Operator - RxJS

 

By Default 同步执行

console.log('script start'); // 1
const obs = new Observable(subscriber => {
  console.log('Observable start'); // 3
  subscriber.next(1); // 4
  subscriber.next(2); // 5
  console.log('Observable end'); // 6
});
obs.subscribe(v => console.log('subscribe', v)); // 2
console.log('script end'); // 8

结果

by default new Observable 的执行都是同步的.

如果是通过 Creation Operators 或者 Join Creation Operators 来创建 Observable, 那就不一定是同步的. 

有一些 operators 创建出来的 Observable default 就是异步的. 比如 timer.

console.log('script start'); // 1
timer(0).subscribe(v => console.log('subscribe', v)); // 3
console.log('script end'); // 2

结果

 

Types of Scheduler

在讲同步 change to 异步之前, 我们先来看看 RxJS 的有多少种 Scheduler

asyncScheduler

它是 setTimeout 这种 level 的, 异步 Macro

asapScheduler

它是 Promise.resolve 这种 level 的, 异步 Micro

animationFrameScheduler

它是 requestAnimationFrame 这种 level 的, 异步 Macro

queueScheduler

它是同步的, 但它可不是默认的同步机制哦, 默认的同步 (当我们没有设置时), 的效果和 queueScheduler 是有微小区别的, 下面会详细讲. 

 

同步 change to 异步

subscribeOn

它是一个 pipe operator, 作用是把 subscribe 这个过程变成异步.

console.log('script start');
const obs = new Observable(subscriber => {
  console.log('Observable start');
  subscriber.next(1);
  subscriber.next(2);
  console.log('Observable end');
});
obs.pipe(subscribeOn(asyncScheduler)).subscribe(v => console.log('subscribe', v));
console.log('script end');

结果

script end 是同步的, Observable start 开始就是异步了. 类似于做了一个 setTimeout 才去 subscribe.

注: subscribeOn 在 pipe 的任何位置效果都是一样的.

observeOn

observeOn 也是 pipe operator, 它和 subscribeOn 都是把同步转换成异步, 但是结果却有很大区别.

console.log('script start');
const obs = new Observable(subscriber => {
  console.log('Observable start');
  subscriber.next(1);
  subscriber.next(2);
  console.log('Observable end');
});
obs
  .pipe(
    tap(() => console.log('tap 1')),
    observeOn(asyncScheduler),
    tap(() => console.log('tap 2'))
  )
  .subscribe(v => console.log('subscribe', v));
console.log('script end');

结果

 

 

注意看, script start 到 script end 都是 同步的, 只有 tap2 开始才是异步.

这是因为 observeOn 的位置是在 pipe tap 2 之前.

所以 observeOn 不像 subscribeOn 那样把全部都变异步, 它只把后续的 stream 变成异步. 之前的依然是同步.

 

Creation Operators with Scheduler

上面是通过 pipe operator 把同步变异步. 还有一种方式是从源头开始变异步.

那就是在 creation operator 加上 parameter scheuduler.

from(obs, asyncScheduler)

console.log('script start');
const obs = new Observable(subscriber => {
  console.log('Observable start');
  subscriber.next(1);
  subscriber.next(2);
  console.log('Observable end');
});
from(obs, asyncScheduler).subscribe(v => console.log(v));
console.log('script end');

效果

我们看看源码了解一下它干了什么

它底层其实是调用了 scheduled. 注意: from + scheduler 已经快废弃了 (RxJS 8 之后就不能这样用了, 改成用 scheduled)

scheduled 会判断 input 是什么类型, 上面的例子是 observable, 所以进入第一个 if, 执行 scheduleObservable

innerFrom 就是没有 scheduler 的 from (这个可没有废弃哦, 废弃的是 from + scheduler), 然后就是加上我们学过的 subscribeOn 和 observeOn.

一下是 4 种区别

console.log('script start');
const obs = new Observable(subscriber => {
  console.log('Observable start');
  subscriber.next(1);
  subscriber.next(2);
  console.log('Observable end');
});
obs.pipe(subscribeOn(asyncScheduler), observeOn(asyncScheduler)).subscribe(v => console.log(v));
console.log('script end');
View Code

结果

记得 scheduled(obs, scheduler) 是 subOn + obsOn

scheduled(array, asyncScheduler)

Array 的处理和 Observable 是不同的哦, Observable 只是加上了 pipe subscribeOn 和 observeOn.

Array 我们继续看源码

然后

 

scheduler.schedule (asyncScheduler)

关键就是这个 scheduler.schedule

上面我们讲了 asyncScheduler 相等于 setTimeout level 的异步(Macro), asapScheduler 相等于 Promose.resolve level 的异步(Micro)

它们底层也确实就是用 setInterval 和 Promise.resolve 来完成的.

asyncScheduler 就是 AsyncScheduler + AsyncAction. 这两个类内部会互相调用对方的方法. 挺乱的.

所有 Scheduler 都继承了 Scheduler class 只是 override 了 Action 和 flush 的逻辑

asyncScheduler.schedule 的接口是

它创建 Action 实例, 然后把 callback work 丢进去, 并调用 Action 的 schedule

 

关键就是这个 setInterval 了. 之后的 flush 就是运行我们传入的 callback 等等. 我们点到为止就好了.

所以呢,

console.log('script start');
asyncScheduler.schedule(() => console.log('call'));
console.log('script end');

结果是

因为里面有一个 setInterval

scheduler.schedule (asapScheduler)

再看一个 AsapAction 例子

它里面就是一个 Promise.resolve

schedule(array, scheduler) vs subscribeOn vs observeOn

 

在 for loop 的时候, 它是把每一个值都做了异步处理.

所以

scheduled([1,2,3], asyncScheduler).subscribe()
from([1,2,3]).pipe(subscribeOn(asyncScheduler), observeOn(asyncScheduler)).subscribe()

这 2 个操作的结果是不一样的. 

scheduled 的 1, 2, 3 每一次 next value 都会进入一个 event loop 都是异步.

而 subscribeOn 只是在延迟了 subscribe, 之后的 next 依然是同步的

而 observeOn 是把前面的流延迟发布下去. 但是源头依然是同步的.

只有 scheduled 是把源头的 1, 2, 3 发布变成了每一次都是延迟. 

 

queueScheduler vs no scheduler

首先 sourceA$ 直接发布 1, 2. 而 sourceB$ 是空的. combineLatest 不会发布

然后 sourceB$ 发布 3, 这时 combineLatest 发布 [2, 3] = 5

然后 sourceB$ 发布 4, 这时 combineLatest 发布 [2, 4] = 6

目前的发布顺序是 a1, a2, b1, b2

有没有可能让它变成 a1, b1, a2, b2 呢? 有, 用 queueScheduler

全部依然是同步的, 只是发布顺序变成了 a1, b1, a2, b2

于是

首先 sourceA$ 发布 1, 而 sourceB$ 是空的. combineLatest 不会发布

然后 sourceB$ 发布 3, 这时 combineLatest 发布 [1, 3] = 4

然后 sourceA$ 发布 2, 这时 combineLatest 发布 [2, 3] = 5

然后 sourceB$ 发布 4, 这时 combineLatest 发布 [2, 4] = 6

 

总结

1. RxJS by default 是同步的

2. 许多 operator 有自己的 scheduler 比如, timer 是 asyncScheduler.

3. asyncScheduler = setInterval level (异步 Macro),

    asapScheduler = Promise.resolve (异步 Micro),  

    animationFrameScheduler = requestAnimationFrame (异步 Macro),

    queueScheduler 是同步但和默认同步有确保

4. pipe operator subscribeOn 是 delay subscribe, 但没有 delay 后续的发布 (放在 pipe 任何位置效果一样)

5. pipe operator observeOn 是 delay 后续的发布, 但是没有 delay 源头 (放在 pipe 的位置不同效果不同)

6. schedule([1,2,3], asyncScheduler) 从源头开始 delay 发布, 效果  1 -> delay -> 2 -> delay -> 3

7. queueScheduler 能修改同步的发布顺序. 通常用在 combineLatest

scheduler 有一点点复杂, 很多时候你不会看出它有啥用. 但是不要紧, 学起来有个印象就好, 需要的时候你自然会用上它.

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章