主题(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 类型:BehaviorSubjectReplaySubjectAsyncSubject

多播 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)到达的时候自动连接,以及当最后一个观察者取消订阅的时候自动取消公共的执行。

考虑下面例子,它的订阅按此列表概述的发生:

  1. 第一个观察者订阅多播 Observable
  2. 多播 Observable 连接
  3. next 发送值 0 给第一个观察者
  4. 第二个观察者订阅多播 Observable
  5. next 发送值 1 给第一个观察者
  6. next 发送值 1 给第一个观察者
  7. 第一个观察者从多播 Observable 取消订阅
  8. next 发送值 2 给第二个观察者
  9. 第二个观察者从多播 Observable 取消订阅
  10. 连接的多播 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)的更多相关文章

  1. RxJS之Subject主题 ( Angular环境 )

    一 Subject主题 Subject是Observable的子类.- Subject是多播的,允许将值多播给多个观察者.普通的 Observable 是单播的. 在 Subject 的内部,subs ...

  2. 【Rxjs】 - 解析四种主题Subject

    原文地址: https://segmentfault.com/a/1190000012669794 引言 开发ngx(angular 2+)应用时,基本上到处都会用到rxjs来处理异步请求,事件调用等 ...

  3. 技术笔记:Indy的TIdSMTP改造,解决发送Html和主题截断问题

    使用Indy来发邮件坑不少啊,只不过有比没有好吧,使用delphi6这种老工具没办法,只能使用了新一点的Indy版本9,公司限制... 1.邮件包含TIdText和TIdAttachment时会出现T ...

  4. [转]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 ...

  5. RxJS v6 学习指南

    为什么要使用 RxJS RxJS 是一套处理异步编程的 API,那么我将从异步讲起. 前端编程中的异步有:事件(event).AJAX.动画(animation).定时器(timer). 异步常见的问 ...

  6. RXJS组件间超越父子关系的相互通信

    RXJS组件间超越父子关系的相互通信 用到这个的需求是这样的: 组件A有数据变化,将变化的数据流通知组件B接收这个数据流并做相应的变化 实例化RXJS的subject对象 import { Injec ...

  7. Angular 非父子组件间的service数据通信

    完成思路:以service.ts(主题subject---订阅sbuscribe模式)为数据中转中间件,通过sku.ts的数据更改监测机制,同步更改service.ts中的数据,同时buy.ts组件实 ...

  8. Angular + Websocket

    Angular使用RxJS,它本质上是一个反应式扩展的javascript实现.这是一个使用可观察序列组成异步和基于事件的程序的库,非常适合使用WebSockets. 简而言之,RxJS允许我们从we ...

  9. Angular 个人深究(二)【发布与订阅】

    Angular 个人深究(二)[发布与订阅] 1. 再入正题之前,首先说明下[ 发布与订阅模式](也叫观察者模式) 1) 定义:定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象.这个 ...

随机推荐

  1. ant design pro 的modal

    通常弹框位置有限,如果一行不下,就拆成两行,例如Table

  2. Pandas | 22 时间差

    时间差(Timedelta)是时间上的差异,以不同的单位来表示.例如:日,小时,分钟,秒.它们可以是正值,也可以是负值.可以使用各种参数创建Timedelta对象,如下所示 - 字符串 通过传递字符串 ...

  3. 靶场sql注入练手----sqlmap篇(纯手打)

    靶场地址:封神台 方法一.首先尝试手工找注入点判断 第一步,判断是否存在sql注入漏洞 构造 ?id=1 and 1=1 ,回车,页面返回正常 构造 ?id=1 and 1=2 ,回车,页面不正常,初 ...

  4. 2019 qbxt CSP-S考前冲刺班总结

    似乎--也没有太多好说的. ​ 但这是最后一次培训,因此还是应该写点什么的. ​ 记得状态最好的一次培训,是高一的第一次培训.那次是总共的第二次培训.第一次去的时候什么也不会,跟的非常吃力,每天都在疯 ...

  5. 【LG2605】[ZJOI2010]基站选址

    [LG2605][ZJOI2010]基站选址 题面 洛谷 题解 先考虑一下暴力怎么写,设\(f_{i,j}\)表示当前\(dp\)到\(i\),且强制选\(i\),目前共放置\(j\)个的方案数. 那 ...

  6. 专题-主存储器与Cache的地址映射方式

    2019/05/02 10:23 首先,我们注意到地址映射有三种:分别是直接地址映射.全相联映射.组相联映射. 首先我们搞清楚主存地址还有Cache地址的关系,还有组内地址的关系,常见我们的块内地址, ...

  7. 从最近的比赛学习CTR/CVR

    https://zhuanlan.zhihu.com/p/35046241 包大人 深度学习炼丹劝退师 278 人赞同了该文章 从最近的比赛学习CTR/CVR 最近在玩kaggle的talking d ...

  8. shell equal

    #!/bin/shzero=0 status=1 let status=0 if [[ $status -eq $zero ]];then echo "bu >= 3.6"e ...

  9. python爬虫实例——爬取歌单

    学习自<<从零开始学python网络爬虫>> 爬取酷狗歌单,保存入csv文件 直接上源代码:(含注释) import requests #用于请求网页获取网页数据 from b ...

  10. Lab2:物理内存管理

    前言 现在内存管理的方法都是非连续内存管理,也就是结合段机制和分页机制 段机制 段地址空间 进程的段地址空间由多个段组成,比如代码段.堆栈段和符号表段等等 段对应一个连续的内存"块" ...