Scheduler 基本观念
不晓得读者们还记不记得,我们在前面的文章中有提到Scheduler 是为了解决RxJS 衍生的最后一个问题,而我们现在就在揭晓这个谜底。
其实RxJS 用久了之后就会发现Observable 有一个优势是可以同时处理同步和非同步行为,但这个优势也带来了一个问题,就是我们常常会搞不清处现在的observable 执行方式是同步的还是非同步的。换句话说,我们很容易搞不清楚observable 到底什么时候开始发送元素!
举例来说,我们可能很清楚interval
是非同步送出元素的,但range
呢?from
呢?他们可能有时候是非同步有时候是同步,这就会变得有点困扰,尤其在除错时执行顺序就非常重要。
而Scheduler 基本上就是拿来处理这个问题的!
什么是Scheduler?
Scheduler 控制一个observable 的订阅什么时候开始,以及发送元素什么时候送达,主要由以下三个元素所组成
- Scheduler 是一个资料结构。它知道如何根据优先级或其他标准来储存并伫列任务。
- Scheduler 是一个执行环境。它意味着任务何时何地被执行,比如像是立即执行、在回呼(callback)中执行、setTimeout 中执行、animation frame 中执行
- Scheduler是一个虚拟时钟。它透过
now()
这个方法提供了时间的概念,我们可以让任务在特定的时间点被执行。
简言之Scheduler 会影响Observable 开始执行及元素送达的时机,比如下面这个例子
var observable = Rx.Observable.create(function (observer) {
observer.next(1);
observer.next(2);
observer.next(3);
observer.complete();
});
console.log('before subscribe');
observable.observeOn(Rx.Scheduler.async) // 设为 async
.subscribe({
next: (value) => { console.log(value); },
error: (err) => { console.log('Error: ' + err); },
complete: () => { console.log('complete'); }
});
console.log('after subscribe');
// "before subscribe"
// "after subscribe"
// 1
// 2
// 3
// "complete"
上面这段程式码原本是同步执行的,但我们用了observable.observeOn(Rx.Scheduler.async)
原本是同步执行的就变成了非同步执行了。
有哪些Scheduler 可以用
目前RxJS 5 Scheduler 跟RxJS 4.x 以前的版本完全不同,在RxJS 5 当中有提供四个scheduler,预设为undefined 会直接以递回的方式执行
- queue
- asap
- async
- animationFrame
这四个scheduler 我们会在下面搭配程式码一一讲解
RxJS 5跟RxJS 4.x预设的Scheduler不同,所以在某些使用情境下会出现不同的结果,例如这个issue,请特别注意。
使用Scheduler
其实我们在使用各种不同的operator时,这些operator就会各自预设不同的scheduler,例如一个无限的observable就会预设为queue
scheduler,而timer相关的operator则预设为async
scheduler。
要使用Scheduler除了前面用到的observeOn()
方法外,以下这几个creation operators最后一个参数都能接收Scheduler
- bindCallback
- bindNodeCallback
- combineLatest
- concat
- empty
- from
- fromPromise
- interval
- merge
- of
- range
- throw
- timer
例如下面这个例子
var observable = Rx.Observable.from([1,2,3,4,5], Rx.Scheduler.async);
另外还有多个operators最后一个参数可以传入Scheduler这边就不一一列出,这已参考官方的文件,最通用的方式还是observeOn()
只要是observable就可以用这个方法。
queue
queue的运作方式跟预设的立即执行很像,但是当我们使用到递回的方法时,他会伫列这些行为而非直接执行,一个递回的operator就是他会执行另一个operator,最好的例子就是repeat()
,如果我们不给他参数的话,他会执行无限多次,像下面这个例子
Rx.Observable.of(10).repeat().take(1)
.subscribe(console.log);
这个例子在RxJS 4.x的版本中执行会使浏览器挂掉,因为take(1)
永远不会被执行到repeat
会一直重复要元素,而在RxJS 5中他预设了无限的observable为queue所以他会把repeat的next行为先伫列起来,因为前一个complete还在执行中,而这时repeat就会回传一个可退订的物件给take(1)
等到repeat的next被第一次执行时就会结束,因为take(1)
会直接收到值。
使用情境:
queue 很适合用在会有递回的operator 且具有大量资料时使用,在这个情况下queue 能避免不必要的效能损耗。
asap
asap 的行为很好理解,它是非同步的执行,在浏览器其实就是setTimeout 设为0 秒(在NodeJS 中是用process.nextTick),因为行为很好理解这里就不写例子了。
使用情境:
asap 因为都是在setTimeout 中执行,所以不会有block event loop 的问题,很适合用在永远不会退订的observable,例如在背景下持续监听server 送来的通知。
async
这个是在RxJS 5 中新出现的Scheduler,它跟asap 很像但是使用setInterval 来运作,通常是跟时间相关的operator 才会用到。
animationFrame
这个相信大家应该都知道,他是利用Window.requestAnimationFrame
这个API去实作的,所以执行周期就跟Window.requestAnimationFrame
一模一样。
使用情境:
在做复杂运算,且高频率触发的UI 动画时,就很适合使用animationFrame,以可以搭配throttle operator 使用。
今日小结
这篇文章简单的介绍了RxJS 的Scheduler,因为篇幅的关系没有办法很细的去讲,但实务上Scheduler 的使用非常简单,只要在operator 的最后一个参数加上去或是用observeOn 就可以了。平常其实不太需要用到Scheduler,尤其在RxJS 5 中已经有针对各种情况给不同的预设,笔者最常用到的还是animationFrame!
不知道今天读者们有没有收获呢?如果有任何疑问,欢迎在下方留言给我,谢谢。