前言

大部分情况下, 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'); // 2
subscriber.next(1); // 3
subscriber.next(2); // 4
console.log('Observable end'); // 5
});
obs.subscribe(v => console.log('subscribe', v)); // 6
console.log('script end'); // 7

结果

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 的

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');

结果

记得 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 有一点点复杂, 很多时候你不会看出它有啥用. 但是不要紧, 学起来有个印象就好, 需要的时候你自然会用上它.

RxJS 系列 – Scheduler的更多相关文章

  1. RxJS——调度器(Scheduler)

    调度器 什么是调度器?调度器是当开始订阅时,控制通知推送的.它由三个部分组成. 调度是数据结构.它知道怎样在优先级或其他标准去存储和排队运行的任务 调度器是一个执行上下文.它表示任务在何时何地执行(例 ...

  2. 第3章 从Flux到Redux

    第3章 从Flux到Redux 3.1 Flux 单向数据流,React是用来替换Jquery的,Flux是以替换Backbone.js.Ember.js等MVC框架为主的. actionTypes. ...

  3. AndroidStudio3.0无法打开Android Device Monitor的解决办法(An error has occurred on Android Device Monitor)

    ---恢复内容开始--- 打开monitor时出现 An error has occurred. See the log file... ------------------------------- ...

  4. [Redux-Observable && Unit Testing] Use tests to verify updates to the Redux store (rxjs scheduler)

    In certain situations, you care more about the final state of the redux store than you do about the ...

  5. 图解 kubernetes scheduler 架构设计系列-初步了解

    资源调度基础 scheudler是kubernetes中的核心组件,负责为用户声明的pod资源选择合适的node,同时保证集群资源的最大化利用,这里先介绍下资源调度系统设计里面的一些基础概念 基础任务 ...

  6. QUARTZ系列之一-基础概念(Scheduler/Job/JobDetail/Trigger)

    摘抄自quartz官方文档: The key interfaces of the Quartz API are: Scheduler - the main API for interacting wi ...

  7. Quartz.Net系列(四):Quartz五大构件(Scheduler,Job,Trigger,ThreadPool、JobStore)之ThreadPool、JobStore解析

    整体示意图: 1.DefaultThreadPool 如果不存在PropertyThreadPoolType,那么就使用DefaultThreadPool var threadPoolTypeStri ...

  8. Angular2入门系列教程7-HTTP(一)-使用Angular2自带的http进行网络请求

    上一篇:Angular2入门系列教程6-路由(二)-使用多层级路由并在在路由中传递复杂参数 感觉这篇不是很好写,因为涉及到网络请求,如果采用真实的网络请求,这个例子大家拿到手估计还要自己写一个web ...

  9. Angular2入门系列教程6-路由(二)-使用多层级路由并在在路由中传递复杂参数

    上一篇:Angular2入门系列教程5-路由(一)-使用简单的路由并在在路由中传递参数 之前介绍了简单的路由以及传参,这篇文章我们将要学习复杂一些的路由以及传递其他附加参数.一个好的路由系统可以使我们 ...

  10. Angular2入门系列教程5-路由(一)-使用简单的路由并在在路由中传递参数

    上一篇:Angular2入门系列教程-服务 上一篇文章我们将Angular2的数据服务分离出来,学习了Angular2的依赖注入,这篇文章我们将要学习Angualr2的路由 为了编写样式方便,我们这篇 ...

随机推荐

  1. 题解:AT_abc359_c [ABC359C] Tile Distance 2

    背景 去中考了,比赛没打,来补一下题. 分析 这道题让我想起了这道题(连题目名称都是连着的),不过显然要简单一些. 这道题显然要推一些式子.我们发现,和上面提到的那道题目一样,沿着对角线走台阶,纵坐标 ...

  2. 学习笔记--Java 运算符

    Java 运算符 算术运算符 关系运算符 逻辑运算符 位运算[略] 赋值运算符 字符串连接符 三元运算符 Java 运算符 按照功能划分: 功能 运算符 算术运算符 +.-.*./.++.--.% 关 ...

  3. C# 网络编程:.NET 开发者的核心技能

    前言 数字化时代,网络编程已成为软件开发中不可或缺的一环,尤其对于 .NET 开发者而言,掌握 C# 中的网络编程技巧是迈向更高层次的必经之路.无论是构建高性能的 Web 应用,还是实现复杂的分布式系 ...

  4. MFC 关于按键状态获取

    alt键会阻断消息? moousemovealt键无法判断,按下一次 并松开一次状态改变一次#define KeyState GetAsyncKeyState BOOL bCtrlDown = (Ke ...

  5. 2024-07-27:用go语言,给定一个正整数数组,最开始可以对数组中的元素进行增加操作,每个元素最多加1。 然后从修改后的数组中选出一个或多个元素,使得这些元素排序后是连续的。 要求找出最多可以选

    2024-07-27:用go语言,给定一个正整数数组,最开始可以对数组中的元素进行增加操作,每个元素最多加1. 然后从修改后的数组中选出一个或多个元素,使得这些元素排序后是连续的. 要求找出最多可以选 ...

  6. ComfyUI插件:ComfyUI Impact 节点(三)

    前言: 学习ComfyUI是一场持久战,而 ComfyUI Impact 是一个庞大的模块节点库,内置许多非常实用且强大的功能节点 ,例如检测器.细节强化器.预览桥.通配符.Hook.图片发送器.图片 ...

  7. C++命名空间、标准输入输出、引用

    1.简述C++中命名空间的作用. 答:避免重复定义全局变量的问题. 2.定义两个命名空间A 和 B 分别在A中和B中定义变量value.在main函数中将两个空间的value打印出来. #includ ...

  8. python对象之间的交互

    python对象之间的交互 先看看一般的类定义如下: class 类名: def __init__(self,参数1,参数2): self.对象的属性1 = 参数1 self.对象的属性2 = 参数2 ...

  9. python 音频处理(1)——重采样、音高提取

    采集数据->采样率调整 使用torchaudio进行重采样(cpu版) 首先导入相关包,既然使用torch作为我们的选项,安装torch环境我就不必多说了,如果你不想用torch可以使用后文提到 ...

  10. Netty的源码分析和业务场景

    Netty 是一个高性能.异步事件驱动的网络应用框架,它基于 Java NIO 构建,广泛应用于互联网.大数据.游戏开发.通信行业等多个领域.以下是对 Netty 的源码分析.业务场景的详细介绍: 源 ...