Scheduler
什么是调度程序?调度程序控制订阅何时开始以及何时发送通知。它由三个部分组成。
-
调度器是一种数据结构。它知道如何根据优先级或其他标准来存储和排队任务。
-
调度程序是一个执行上下文。它表示任务在何处和何时执行(例如立即执行,或在其他回调机制中,例如 setTimeout 或 process.nextTick,或动画帧)。
-
调度程序有一个(虚拟)时钟。它通过调度程序上的 getter 方法 now() 提供了“时间”的概念。在特定调度程序上调度的任务将仅遵守该时钟指示的时间
调度器允许你定义一个 Observable 将在什么执行上下文中向它的 Observer 发送通知。
在下面的示例中,我们采用通常的简单 Observable 同步发出值 1、2、3,并使用运算符 observeOn 指定用于传递这些值的异步调度程序。
import { Observable, asyncScheduler } from 'rxjs';
import { observeOn } from 'rxjs/operators';
const observable = new Observable((observer) => {
observer.next(1);
observer.next(2);
observer.next(3);
observer.complete();
}).pipe(
observeOn(asyncScheduler)
);
console.log('just before subscribe');
observable.subscribe({
next(x) {
console.log('got value ' + x)
},
error(err) {
console.error('something wrong occurred: ' + err);
},
complete() {
console.log('done');
}
});
console.log('just after subscribe');
与输出一起执行:
just before subscribe
just after subscribe
got value 1
got value 2
got value 3
done
注意通知是如何获得价值的...在订阅后立即传递,这与我们目前看到的默认行为不同。这是因为observeOn(asyncScheduler) 在新的Observable 和最终的Observer 之间引入了一个代理Observer。让我们重命名一些标识符以在示例代码中明显区分:
import { Observable, asyncScheduler } from 'rxjs';
import { observeOn } from 'rxjs/operators';
var observable = new Observable((proxyObserver) => {
proxyObserver.next(1);
proxyObserver.next(2);
proxyObserver.next(3);
proxyObserver.complete();
}).pipe(
observeOn(asyncScheduler)
);
var finalObserver = {
next(x) {
console.log('got value ' + x)
},
error(err) {
console.error('something wrong occurred: ' + err);
},
complete() {
console.log('done');
}
};
console.log('just before subscribe');
observable.subscribe(finalObserver);
console.log('just after subscribe');
proxyObserver 是在 observeOn(asyncScheduler) 中创建的,其 next(val) 函数大致如下:
const proxyObserver = {
next(val) {
asyncScheduler.schedule(
(x) => finalObserver.next(x),
0 /* delay */,
val /* will be the x for the function above */
);
},
// ...
}
异步调度程序使用 setTimeout 或 setInterval 运行,即使给定的延迟为零。像往常一样,在 JavaScript 中,已知 setTimeout(fn, 0) 在下一次事件循环迭代中最早运行函数 fn。这解释了为什么在 subscribe 发生后立即将获得的值 1 传递给 finalObserver。
Scheduler 的 schedule() 方法接受一个 delay 参数,它指的是相对于 Scheduler 自己的内部时钟的时间量。调度程序的时钟不需要与实际挂钟时间有任何关系。这就是像延迟这样的时间操作符不是按实际时间运行,而是按调度程序时钟规定的时间运行的方式。这在测试中特别有用,其中虚拟时间调度程序可用于伪造挂钟时间,而实际上同步执行计划任务。
Scheduler Types 调度程序类型
异步调度器是 RxJS 提供的内置调度器之一。每一个都可以通过使用 Scheduler 对象的静态属性来创建和返回。
| SCHEDULER | PURPOSE |
| --- | --- |
| null | 通过不传递任何调度程序,通知以同步和递归的方式传递。将此用于恒定时间操作或尾递归操作。 |
| queueScheduler | 在当前事件框架中的队列上调度(蹦床调度程序)。将此用于迭代操作。 |
| asapScheduler |微任务队列上的调度,与用于承诺的队列相同。基本上在当前工作之后,但在下一个工作之前。将此用于异步转换。 |
| asyncScheduler | 计划与 setInterval 一起使用。将此用于基于时间的操作。 |
| animationFrameScheduler | 安排将在下一次浏览器内容重绘之前发生的任务。可用于创建流畅的浏览器动画。 |
Using Schedulers
你可能已经在你的 RxJS 代码中使用了调度器,而没有明确说明要使用的调度器的类型。这是因为所有处理并发的 Observable 运算符都有可选的调度程序。如果你不提供调度器,RxJS 会按照最小并发的原则选择一个默认的调度器。这意味着选择引入最少并发量以满足运营商需求的调度器。例如,对于返回带有有限和少量消息的 observable 的操作符,RxJS 不使用调度程序,即 null 或 undefined。对于返回潜在大量或无限数量消息的运算符,使用队列调度程序。对于使用计时器的运算符,使用 async。
因为 RxJS 使用最少的并发调度器,如果你想为了性能目的引入并发,你可以选择不同的调度器。要指定特定的调度程序,您可以使用那些采用调度程序的运算符方法,例如 from([10, 20, 30], asyncScheduler)。
静态创建运算符通常将调度程序作为参数。 例如, from(array, scheduler) 允许您指定在传递从数组转换而来的每个通知时要使用的调度程序。它通常是运算符的最后一个参数。以下静态创建运算符采用 Scheduler 参数:
-
bindCallback
-
bindNodeCallback
-
combineLatest
-
concat
-
empty
-
from
-
fromPromise
-
interval
-
merge
-
of
-
range
-
throw
-
timer
使用 subscribeOn 来安排 subscribe() 调用在什么上下文中发生。 默认情况下,对 Observable 的 subscribe() 调用将同步且立即发生。但是,您可以使用实例运算符 subscribeOn(scheduler) 延迟或安排实际订阅发生在给定的 Scheduler 上,其中 scheduler 是您提供的参数。
使用observeOn 来安排发送通知的上下文。 正如我们在上面的例子中看到的,实例操作符 observeOn(scheduler) 在源 Observable 和目标观察者之间引入了一个中介观察者,中介者使用你给定的调度程序调度对目标观察者的调用。
实例运算符可以将调度程序作为参数。
bufferTime、debounceTime、delay、auditTime、sampleTime、throttleTime、timeInterval、timeout、timeoutWith、windowTime等时间相关的操作符都以一个Scheduler作为最后一个参数,否则默认在asyncScheduler上运行。
其他以 Scheduler 为参数的实例操作符:cache、combineLatest、concat、expand、merge、publishReplay、startWith。
请注意,缓存和发布重放都接受调度程序,因为它们使用了重播主题。 ReplaySubjects 的构造函数将可选的 Scheduler 作为最后一个参数,因为 ReplaySubject 可能会处理时间,这仅在 Scheduler 的上下文中才有意义。默认情况下,ReplaySubject 使用队列调度程序来提供时钟。