Scheduler

516 阅读5分钟

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 使用队列调度程序来提供时钟。