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 有一點點複雜, 很多時候你不會看出它有啥用. 但是不要緊, 學起來有個印象就好, 需要的時候你自然會用上它.

 

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