基本概念
Schedulers 是用来控制并发并且是中央集权的调度员,允许我们在发生计算时进行协调,例如 setTimeout
或 requestAnimationFrame
或其他。
- 调度器是一种数据结构。它知道如何根据优先级或其他标准来存储任务和将任务进行排序。
- 调度器是执行上下文。它表示在何时何地执行任务 (举例来说,立即的,或另一种回调函数机制 (比如
setTimeout
或process.nextTick
),或动画帧)。 - 调度器有一个 (虚拟的) 时钟。调度器功能通过它的
getter
方法now()
提供了 “时间” 的概念。在具体调度器上安排的任务将严格遵循该时钟所表示的时间。
学到这相信大家也已经或多或少对 RxJS 有一定了解了,不知道大家有没有发现一个疑问,前面所展示的代码示例中有同步也有异步,而笔者却没有显示的控制他们的执行,他们的这套执行机制到底是什么呢?
其实他们的内部的调度就是靠的 Schedulers 来控制数据发送的时机,许多操作符会预设不同的 Scheduler,所以我们不需要进行特殊处理他们就能良好的进行同步或异步运行。
const source = Rx.Observable.create(function (observer: any) {
observer.next(1);
observer.next(2);
observer.next(3);
observer.complete();});
console.log('订阅前');
source.observeOn(Rx.Scheduler.async) // 设为 async
subscribe({
next: (value) => { console.log(value); },
error: (err) => { console.log('Error: ' + err); },
complete: () => { console.log('complete'); }
});
console.log('订阅后');
// 订阅前
// 订阅后
// 1
// 2
// 3
// complete
从打印结果上来看,数据的发送时机的确已经由同步变成了异步,如果不进行调度方式修改,那么 “订阅后” 的打印应该是在数据发送完毕之后才会执行的。
调度方法
看完示例之后我们再来研究这个调度器能做哪几种调度:
当没有延迟使用时,它将同步安排给定的任务 - 在安排好任务后立即执行。但是,当递归调用时(即在已调度的任务内部),将使用队列调度程序调度另一个任务,而不是立即执行,该任务将被放入队列并等待当前任务完成。
这意味着,当您使用 queue 调度程序执行任务时,您确定它会在该调度程序调度的其他任何任务开始之前结束。
这个同步与我们平常理解的同步可能不太一样,笔者当时也都困惑了一会。
还是用一个官方的例子来讲解这种调度方式是怎么理解吧:
import { queueScheduler } from 'rxjs';
queueScheduler.schedule(() => {
queueScheduler.schedule(() => console.log('second'));
console.log('first');
});
我们无需关注陌生的函数调用,我们这里着重于看这种调度方式与平常的同步调度的区别。
首先我们调用 queueScheduler 的 schedule 方法开始执行,然后函数内部又同样再以同样的方式调用(这里也可以改成递归,不过这里用这个示例去理解可能会好一点),并且传入一个函数,打印 second。
然后继续看下面的语句,一个普通的 console.log('first')
,然后我们再来看看打印结果:
// first
// second
是不是有点神奇,如果没看明白为啥的,可以再回头看看前面 queue 对于递归执行的处理方式。也就是说如果递归调用,它内部会维护一个队列,然后等待先加入队列的任务先执行完成(也就是上面的 console.log('first')
执行完才会执行 console.log('second')
,因为 console.log('second')
这个任务是后加入该队列的)。
asap
内部基于 Promise 实现(Node 端采用 process.nextTick
),他会使用可用的最快的异步传输机制,如果不支持 Promise 或 process.nextTick
或者 Web Worker 的 MessageChannel 也可能会调用 setTimeout
方式进行调度。
async
与 asap 方式很像,只不过内部采用 setInterval 进行调度,大多用于基于时间的操作符。
animationFrame
从名字看其实相信大家已经就能略知一二了,内部基于 requestAnimationFrame
来实现调度,所以执行的时机将与 window.requestAnimationFrame
保持一致,适用于需要频繁渲染或操作动画的场景。