RxJS——主题(Subject)
主题(Subjects)
什么是主题?RxJS 主题就是一个特性类型的 Observable 对象,它允许值多路广播给观察者(Observers)。当一个简单的 Observable 是单播的(每个订阅的观察者它们自己都依赖 Observable 的执行)时候,主题(Subjects)就是多播的。
Subjects 就像是一个 Observable,但是它能多播到多个观察者(Observers)。Subjects 就像是事件发射器:它们维护众多侦听者的注册。
每一个 Subject 都是一个 Observable。给定一个 Subject,你就能订阅它,提供一个 Observer,开始正常接收值。从 Observer 它的角度讲,它不知道 Observable 的执行是否来自普通的单播 Observable 或是 Subject 。
在 Subject 内部,subscribe
不会调用新的执行来发送值。它只是简单的在观察者列表中注册一个观察者,跟在其他库和语言中的 addListener
的做法是很相似的。
每个 Subject 也是一个 Observer。它通过 next(v)
,error(e)
,complete()
是一个对象。为了给 Subject 提供一个新值,只需要调用 next(theValue)
,那么它将会多播给注册侦听到 Subject 的观察者。
下面是一个例子,我们有附加了两个观察者对象,并且我们发送一些值给 Subject:
import { Subject } from 'rxjs';
const subject = new Subject<number>();
subject.subscribe({
next: (v) => console.log(`observerA: ${v}`)
});
subject.subscribe({
next: (v) => console.log(`observerB: ${v}`)
});
subject.next(1);
subject.next(2);
//Logs:
// observerA: 1
// observerB: 1
// observerA: 2
// observerB: 2
因为 Subject 是一个观察者,也就是说你也许会提供一个 Subject 作为参数给 subscribe
到任何 Observable,就像下面这个例子:
import { Subject, from } from 'rxjs';
const subject = new Subject<number>();
subject.subscribe({
next: (v) => console.log(`observerA: ${v}`)
})
subject.subscribe({
next: (v) => console.log(`observerB: ${v}`)
});
const observable = from([1, 2, 3]);
observable.subscribe(subject); // 你可以订阅已经提供的 observable 对象
// Logs:
// observerA: 1
// observerB: 1
// observerA: 2
// observerB: 2
// observerA: 3
// observerB: 3
通过上面的方法,本质上我们就仅仅只是通过 Subject 把单播的可观察的执行转成了多播的。这个例子演示了主题如何让多个观察者共享 Observable 的执行的唯一方法。
这里还有一些特殊的 Subject 类型:BehaviorSubject
,ReplaySubject
,AsyncSubject
。
多播 Observables
一个 “多播Observable” 通过一个 Subject 传递通知,它可能会有很多订阅者,而一个普通的 “单播 Observable” 只会发送通知到单个观察者。
一个多播 Observable 在后台(hood) 用一个 Subject 让多个观察者都能看到相同的 Observable 执行。
在后台,multicast
又是如何工作的呢:观察者订阅一个基础的 Subject,并且这个 Subject 订阅了源 Observable。下面的例子跟上面的例子很相似,它使用了 observable.subscribe(subject)
:
import { from, Subject } from 'rxjs';
import { multicast } from 'rxjs/operators';
const source = from([1, 2, 3]);
const subject = new Subject();
const multicasted = source.pipe(multicast(subject));
//这里在后台就是 `subject.subscribe({...})`
multicasted.subscribe({
next: (v) => console.log(`observableA: ${v}`);
});
multicasted.subscribe({
next: (v) => console.log(`observableB: ${v}`);
});
//这个带后台就是 `source.subscribe(subject)`
multicasted.connect();
multicast
返回一个看起来想平常使用的 Observable,但是工作却像 Subject,当它订阅的时候。multicast
返回的实际是 ConnectableObservable
,它只是一个使用 connect()
方法的 Observable。
当那些共享的 Observable 的执行开始执行的时候 connect()
方法明确执行是非常重要的。因为 connect()
会在后台执行 source.subscribe(subject)
,connect()
返回一个 Subscription,它使你能够取消订阅,从而取消那些共享的 Observable 的执行。
引用计数(Reference counting)
手动调用 connect()
处理订阅(Subscription)是很麻烦的。通常,我们想要当第一个观察者(Observer)到达的时候自动连接,以及当最后一个观察者取消订阅的时候自动取消公共的执行。
考虑下面例子,它的订阅按此列表概述的发生:
- 第一个观察者订阅多播 Observable
- 多播 Observable 连接
next
发送值 0 给第一个观察者- 第二个观察者订阅多播 Observable
next
发送值 1 给第一个观察者next
发送值 1 给第一个观察者- 第一个观察者从多播 Observable 取消订阅
next
发送值 2 给第二个观察者- 第二个观察者从多播 Observable 取消订阅
- 连接的多播 Observable 取消订阅
为了达成上述过程,显示调用 connect()
,我们编写如下代码:
import { interval, Subject } from 'rxjs';
import { multicast } from 'rxjs/operators';
const source = interval(500);
const subject = new Subject();
const multicasted = source.pipe(multicast(subject));
let subscription1, subscription2, subscriptionConnect;
subscription1 = multicasted.subscribe({
next: (v) => console.log(`observableA: ${v}`)
});
//这里应该调用 `connect()`,因为第一个订阅者订阅了 `multicasted`,它正在对消费的值感兴趣
subscriptionConnect = multicasted.connect();
setTimeout(() => {
subscription2 = multicasted.subscribe({
next: (v) => console.log(`observableB: ${v}`)
});
},600);
setTimeout(() => {
subscription1.unsubsribe();
},1200);
//这里我们应该取消订阅公共的 Observable 的执行
setTimeout(() => {
subscription2.unsubscribe();
subscriptionConnect.unsubscribe(); //这个是针对公共的 Observable 的执行
}, 2000);
如果我们希望避免显示调用 connect()
,我们可以使用 ConnectableObservable.refCount()
(引用计数)方法,它返回一个 Observable,而且它还是可以追踪它有的所有订阅者。当订阅者们的这个数字从 0 增到 1,它就会自动调用 connect()
,开始公共的执行。只有当计数从 1 到 0 时才会整个取消订阅,停止所有的执行。
refCount 使多播 Observable 当第一个订阅者到达的时候自动开始执行,并且最后一个离开的时候停止执行。
下面是例子:
import { interval, Subject } from 'rxjs';
import { multicast, refCount } from 'rxjs/operators';
const source = interval(500);
const subject = new Subject();
const refCounted = source.pipe(multicast(subject), refCount());
let subscription1, subscription2;
//这里自动调用 `connect()`,因为第一个 ‘refCounted’ 的订阅者
console.log('observerA subscribed');
subscription1 = refCounted.subscribe({
next: (v) => console.log(`observableA: ${v}`)
});
sutTimeout(() => {
console.log('observerB subscribed');
subscription2 = refCounted.subscribe({
next: (v) => console.log(`observableB ${v}`)
});
}, 600);
setTimeout(() => {
console.log('observerA unsubscribed');
subscription1.unsubscribe();
}, 1200);
//这里公共的 Observable 的执行将会停止,因为 'refCounted' 在这之后没有订阅者了
setTimeout(() => {
console.log('observerB unsubscribed');
subscription2.unsubscribe();
}, 2000);
//Logs
// observerA subscribed
// observerA: 0
// observerB subscribed
// observerA: 1
// observerB: 1
// observerA unsubscribed
// observerB: 2
// observerB unsubscribed
refCounted()
方法只存在于 ConnectableObservable
对象中,并且它返回的是一个 Observable 而不是 ConnectableObservable
。
行为主题(BehaviorSubject)
有一个 Subjects 的变体就是 BehaviorSubject
,它有一个 “当前值” 的概念。它会存储最近的发送给消费者的一个值,无论这个新的观察者是否订阅,它都将会立即从 BehaviorSubject
接收这个 “当前值”。
BehaviorSubject 对于表示 “过程值(values over time)” 是很有用的。例如一个表示生日的事件流是一个 Subject,那么这个人的年龄的流将是一个 BehaviorSubject
看下面的例子,BehaviorSubject 初始化为 0,它在第一个观察者接收这个值的时候开始订阅。第二个观察者接收值 2,即使它在这个值 2 发送之后被订阅的。
import { BehaviorSubject } from 'rxjs';
const subject = new BehaviorSubject(0);
subject.subject({
next: (v) => console.log(`observerA: ${v}`)
});
subject.next(1);
subject.next(2);
subject.subscribe({
next: v => console.log(`observerB: ${v}`)
});
subject.next(3);
// Logs
// observerA: 0
// observerA: 1
// observerA: 2
// observerB: 2
// observerA: 3
// observerB: 3
重播主题(ReplaySubject)
应答主题很像 BehaviorSubject
,它能发送旧的值给新的订阅者,但是它也能记录部分 Observable 的执行。
ReplaySubject 从 Observable 的执行中记录多个值并且重新把这些值发送给新的订阅者
当创建一个 ReplaySubject
时,你可以指定这些如何重播:
import { ReplaySubject } from 'rxjs';
const subject = new ReplaySubject(3); // 缓冲 3 个值给新的订阅者
subject.subscribe({
next: v => console.log(`observerA: ${v}`)
});
subject.next(1);
subject.next(2);
subject.next(3);
subject.next(4);
subject.subscribe({
next: v => console.log(`observerB: ${v}`)
});
subject.next(5);
// Logs:
// observerA: 1
// observerA: 2
// observerA: 3
// observerA: 4
// observerB: 2
// observerB: 3
// observerB: 4
// observerA: 5
// observerB: 5
你也可以缓存大小里指定一个窗口时间,来确定记录那些值的年龄。在下面的代码中,我们使用 100 大小的缓冲,但是窗口时间参数是 500 毫秒。
import { ReplaySubject } from 'rxjs';
const subject = new ReplaySubject(100, 500 /* 窗口时间 */);
subject.subscribe({
next: v => console.log(`observerA: ${v}`)
});
let i = 1;
setInterval(() => subject.next(i++), 200);
setTimeout(() => {
subject.subscribe({
next: v => console.log(`observerB: ${v}`)
})
}, 1000);
// Logs
// observerA: 1
// observerA: 2
// observerA: 3
// observerA: 4
// observerA: 5
// observerB: 3
// observerB: 4
// observerB: 5
// observerA: 6
// observerB: 6
// ...
异步主题(AsyncSubject)
AsyncSubject 是一个变体,它只会发送 Observable 的执行的最后一个值给观察者们,并且只当执行完成的时候。
import { AsyncSubject } from 'rxjs';
const subject = new AsyncSubject();
subject.subscribe({
next: (v) => console.log(`observerA: ${v}`)
})
subject.next(1);
subject.next(2);
subject.next(3);
subject.next(4);
subject.subscribe({
next: (v) => console.log(`observerB: ${v}`)
});
subject.next(5);
subject.complete();
// Logs:
// observerA: 5
// observerB: 5
AsyncSubject 跟 last()
操作符相似,它等待 complete
通知以便于发送一个值。
RxJS——主题(Subject)的更多相关文章
- RxJS之Subject主题 ( Angular环境 )
一 Subject主题 Subject是Observable的子类.- Subject是多播的,允许将值多播给多个观察者.普通的 Observable 是单播的. 在 Subject 的内部,subs ...
- 【Rxjs】 - 解析四种主题Subject
原文地址: https://segmentfault.com/a/1190000012669794 引言 开发ngx(angular 2+)应用时,基本上到处都会用到rxjs来处理异步请求,事件调用等 ...
- 技术笔记:Indy的TIdSMTP改造,解决发送Html和主题截断问题
使用Indy来发邮件坑不少啊,只不过有比没有好吧,使用delphi6这种老工具没办法,只能使用了新一点的Indy版本9,公司限制... 1.邮件包含TIdText和TIdAttachment时会出现T ...
- [转]VS Code 扩展 Angular 6 Snippets - TypeScript, Html, Angular Material, ngRx, RxJS & Flex Layout
本文转自:https://marketplace.visualstudio.com/items?itemName=Mikael.Angular-BeastCode VSCode Angular Typ ...
- RxJS v6 学习指南
为什么要使用 RxJS RxJS 是一套处理异步编程的 API,那么我将从异步讲起. 前端编程中的异步有:事件(event).AJAX.动画(animation).定时器(timer). 异步常见的问 ...
- RXJS组件间超越父子关系的相互通信
RXJS组件间超越父子关系的相互通信 用到这个的需求是这样的: 组件A有数据变化,将变化的数据流通知组件B接收这个数据流并做相应的变化 实例化RXJS的subject对象 import { Injec ...
- Angular 非父子组件间的service数据通信
完成思路:以service.ts(主题subject---订阅sbuscribe模式)为数据中转中间件,通过sku.ts的数据更改监测机制,同步更改service.ts中的数据,同时buy.ts组件实 ...
- Angular + Websocket
Angular使用RxJS,它本质上是一个反应式扩展的javascript实现.这是一个使用可观察序列组成异步和基于事件的程序的库,非常适合使用WebSockets. 简而言之,RxJS允许我们从we ...
- Angular 个人深究(二)【发布与订阅】
Angular 个人深究(二)[发布与订阅] 1. 再入正题之前,首先说明下[ 发布与订阅模式](也叫观察者模式) 1) 定义:定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象.这个 ...
随机推荐
- django -- ORM实现作者增删改查
前戏 前面我们已经实现了出版社的增删改查,书的增删改查,书和出版社的对应关系.现在来写一下作者的增删改查和书的对应关系,那书和作者有什么关系呢?一个作者可以写多本书,一本书可以有多个作者,所以书和作者 ...
- idea输出文件夹没有jsp页面
目录 idea输出文件夹没有jsp页面 问题描述 解决办法 idea输出文件夹没有jsp页面 问题描述 开始创建没有使用web的模板, 自己创建tomcat等配置, 后来启动发现没有index.jsp ...
- 未能加载文件或程序集“***”或它的某一个依赖项,拒绝访问 <解决方法>
报错信息如下: 注:为了部分隐私安全需要,已将有问题文件名替换为filename,系统win2008R2,Microsoft .NET Framework 版本:4.0.30319; ASP.NET ...
- Spring Boot进阶系列四
这边文章主要实战如何使用Mybatis以及整合Redis缓存,数据第一次读取从数据库,后续的访问则从缓存中读取数据. 1.0 Mybatis MyBatis 是支持定制化 SQL.存储过程以及高级映射 ...
- Python(二)Marshmallow 库相关学习
0. 前言 Marshmallow 是一个用于将 ORM 对象与 Python 原生数据类型之间转换的库.实现 object → dict.object → list.string → dict 和 ...
- [技术博客]海报图片生成——小程序canvas画布
目录 背景介绍 canvas简介 代码实现 难点讲解 圆角矩形裁剪失败之PS的妙用 编码不要过硬 对过长的文字进行截取 真机首次生成时字体不对 drawImage只能使用本地图片 背景介绍 目标:利用 ...
- jdk 1.6 新特性
JDK1.6新特性 1.DestTop类和SystemTray类 前者用于调度操作系统中的一些功能,例如: · 可以打开系统默认浏览器指定的URL地址: · 打开系统默认邮件客户端给指定的邮箱发信息: ...
- POJ-最大连续子序列和
给定一个整数序列,找到一个具有最大和的连续子序列(子序列最少包含一个元素),返回其最大和. 实例输入: -2, 1, -3, 4, -1, 2, 1, -5, 4 实例输出: 6(连续子序列4, -1 ...
- python docker api
开启Remote API docker默认是没有开启Remote API的,需要我们手动开启.编辑/lib/systemd/system/docker.service文件, 在文件里的ExecStar ...
- xcode红色文件夹或文件解决方法
文件夹或文件变红是找不到文件导致,解决方法如下: 选中红色的文件或者文件夹,在最右边出现菜单里面有个Location,下一行有个文件夹按钮,点击选择正确的文件路径或者文件就可以了.