前言

RxJS 最大篇幅就是一堆的 operators, 但是那些概念并不多, 只要常用就能熟能生巧了.

Observable 和 Subject 反而需要我们了解清楚.

所以这篇我们先来了解这 2 大概念的其中一个 Observable.

参考

Docs – Observable

Observable / RxJS 的使用场景

因为 RxJS 基本上就是 Observable, 所以为什么要用 Observable 相等于为什么要用 RxJS.

在使用 JS, DOM, BOM 的时候, 我们经常面对不同的接口, 比如 addEventListener, Intersection Observer, XMLHttpRequest 等等

它们使用的 API 都不一样, 但都有一个共同的特性, 那就是都有 callback, 都会触发多次.

这时 RxJS 就可以作为一个统一的管理手段. 这个和 C# 的 LINQ 是一样的. LINQ 的诞生就是为了统一所有对集合的操作. 不管你是 DataTable, Array, List 最终都可以使用 LINQ 统一接口来操作.

如何理解 Observable

我们在学习新东西时, 往往会从它长得像以前认识的某些东西作为入手. 但如果一个新东西跟以前许多东西像, 但又不像时, 这个方法反而会制造出混乱.

从概念上去理解 Observable 就有点这种混乱的感觉. 因为它带有 Generator Function 的某些特性, 但又缺失一些特性, 然后它又有点观察者模式的味道, 但又不完全是.

所以呢, 我们干脆先把它当作新物种. 在慢慢把旧事物套进去, 这样来学习它.

Observable 和 观察者模式

Observable 创建与订阅

Observable 是一个 class, 创建的方式就是 new

const observable = new Observable();
observable.subscribe(() => {
console.log('calling');
});

顾名思义, 它肯定和观察者模式有关系. 在看看它的方法 .subscribe()

subscribe 就是订阅, 和 addEventListener 差不多意思.

但上面的代码中, 只表达了订阅的逻辑, 也就是当事件触发后想做些什么. 但却没有表明事件什么时候会触发.

所以下面这个才是完整的 Observable

const observable = new Observable<string>(subscriber => {
setInterval(() => {
subscriber.next('passing event');
}, 2000);
});
observable.subscribe(event => {
console.log('calling', event);
});

new Observable 时传入一个方法. 这个方法会在 subscribe 时被调用. 通过参数得到 subscriber 对象.

在这个方法内, 就可以表达 event 发布的逻辑. 比如 setInterval 让它每一秒发布一次 subscriber.next 就是 dispatch event 的意思.

至此, 一个完整的订阅和发布就诞生了. 它看上去就是个观察者模式.

Multiple Subscribe

从订阅发布逻辑来看, 它确实就是观察者模式的体现. 但是它有一点很特别.

当我们 2 次调用 subscribe 时, 会发现它的表现就和典型的观察者模式不一样了.

observable.subscribe(event => {
console.log('calling1', event);
});
observable.subscribe(event => {
console.log('calling2', event);
});

典型的观察者, 会把所以订阅方法放到一块, 当时间触发时挨个挨个去调用.

但 subscribe 却不同. 每一次调用 subscribe, Observable 的初始方法都会被调用. 然后生成一个独立的时间线.

像上面的例子, 2 次 subscribe 诞生了 2 个独立的 interval. 它们发布的时机是不同的.

以上就是 Observable 又像又不像观察者模式的几个点.

Observable 和 Generator Function

上面说到, 每一次调用 subscribe 都会产生独立的 stream (每一次初始化方法都会执行一遍).

这个跟 Generator Function 有点像. 哪里像? 2 个点

1. 调用函数, 返回多次值. Generator Function 和普通函数最大的区别是它可以返回多次值.

2. 每一次调用得到的返回都是相互独立的.

那哪里不一样呢? 一个是 pull 一个是 push.

参考: RxJS——Observable 和生成器,推模式和拉模式

Generator Function 属于 pull, 调用函数后会得到 iterator, 当想获取值的时候 for...of iterator 就可以了. 所以主导权在调用方.

Observable 属于 push, 调用函数后不会得到 iterator, 相反它是提供了一个 callback 方法给发布方. 然后静静等待发布方发布值.所以主导权在发布方.

以上就是 Observable 又像又不像 Generator Function 的几个点.

小结

综上, Observable 带有观察的订阅发布的概念, 但它每一次订阅都执行初始化函数, 形成独立的 stream,

后半段这点和 Generator Function 很像, 唯一的区别是 pull 和 push 主导权的不同.

同步 和 异步

由于大部分 RxJS 都是用来处理异步事件. 这让我们以为 RxJS 和 Promise 一样一定是异步的. 但其实不然.

import { Observable } from 'rxjs';

const observable = new Observable(subscriber => {
console.log('1');
subscriber.next();
console.log('3');
}); console.log('0');
observable.subscribe(() => {
console.log('2');
}); console.log('4');

上面是一个同步执行的例子. 如果初始化函数里面用到了异步方法, 比如 setTimeout, 那么它才会变成异步的

next, error, complete

上面提到了 next 相等于 dispatch (发布). 其实还有 2 个特别的 dispatch, 一个是 complete, 一个是 error.

complete 表示这个 stream 结束了, 再也不会有值发布了.

error 则表示 stream 内部报错了, error 以后 stream 也结束了, 不能再发送任何值.

const observable = new Observable<string>(subscriber => {
subscriber.next('passing event');
subscriber.error('error');
subscriber.complete();
return () => console.log('dispose');
}); observable.subscribe({
next: () => {
console.log('next');
},
complete: () => {
console.log('complete');
},
error: () => {
console.log('error');
},
});

当 dispatch error 以后, complete 不会触发, dispose 会触发. 上面的 console 是 next > error > end

如果把 error 注释掉, 那么它的 console 是 next > complete > end

如何退订 subscribe 与 如何 dispose Observable

unsubscribe

const subscription = observable.subscribe(event => {
console.log('calling1', event);
});
subscription.unsubscribe();

调用 subscribe 以后会返回一个 subscription 对象. 这个对象就带有 unsubscribe 方法.

如果有 multiple subscription 也可以集合起来一起 unsubscribe.

const subscriptionCollection = new Subscription(); // 集合
const subscription = observable.subscribe(event => {
console.log('calling1', event);
});
subscriptionCollection.add(subscription); // 添加
subscriptionCollection.unsubscribe(); // 一起退订

Subscription 还有一个 closed 属性, 用来判断 stream 是否已经 completed, error 或者 unsubscribe。

提醒:如果把一个 subscription add 进一个已经 unsubscribe 的 Subscription 对象里,它会马上被 unsubscribe。

Observable dispose

上面提到, 每一次 subscribe 都会开启一个新的 Interval. 这些资源应该在退订后得到释放. 这就是 dispose 的职责.

const observable = new Observable<string>(subscriber => {
const interval = setInterval(() => {
subscriber.next('passing event');
}, 2000);
// 返回 dispose 方法
return () => {
clearInterval(interval); // 释放资源
}
});

初始化方法可以返回一个 dispose 方法. 当退订后这个方法会被执行. 于是里面就可以做一些释放资源的事情了。

Listening to unsubscribe event by finalize operator

unsubscribe 不会导致 Observable 发布 next, error, complete,只会执行初始化方法返回的 dispose 方法。

如果我们无法添加逻辑代码到 dispose 方法里,但有希望在 unsubscribe 时做点事情,那我们可以使用 finalize operator。

如果我们想知道一个 Observable 被 unsubscribe 了的话,需要通过 finalize 功能

Create Observable by Operators

参考

RxJS 建立類型 Operators (1) - EMPTY / of / range / iif / throwError / ajax

RxJS 建立類型 Operators (2) - from / fromEvent / fromEventPattern / interval / timer / defer

Docs – Creation Operators

new Observable 是底层创建 Observable 的手法. RxJS 封装了许多 Operators 方便我们创建 Observable.

所以在开发项目时, 直接 new Observable 是比较少见的.

以下都是 RxJS 封装好的方法.

下面是我常用的

fromEvent

它用来替代 addEventListener

const buttonClicked$ = fromEvent(document.querySelector('button')!, 'click');
buttonClicked$.subscribe(() => {
console.log('clicked');
});

fromEventPattern

它是底层的 fromEvent, fromEvent 的 target 必须实现 interface addEventListener, 但偶尔会遇上一些 target 没有实现这个 interface.

这时就只能用 fromEventPattern 来自定义如何实现 addEventListener 和 removeEventListener 了.

const button = document.querySelector('button')!;
const clicked$ = fromEventPattern(
handler => {
button.addEventListener('click', handler);
},
handler => {
button.removeEventListener('click', handler);
}
);
const sub = clicked$.subscribe(event => console.log('clicked', event));
sub.unsubscribe();

interval

用来替代 setInterval

const interval$ = interval(1000);
interval$.subscribe(count => {
console.log(count); // 0..1..2..3
});

当 unsubscribe 后, 内部的 interval 也会自动被释放, RxJS 替我们封装好了.

timer

用来替代 setTimeout 和实现 delayable interval

timer 是 interval 的底层实现,使用它,我们可以控制的更多。

timer 有 2 个参数

第一个是 delay

第二个是 duration

对比 interval 只有 duration, 多了 delay 功能. interval 的行为是这样的, 如果 duration 是 1 秒, 那么第一次触发就是在 1 秒后, 然后第二次又再一秒后.

timer 的 delay 可以控制第一次的 1 秒.

timer(0, 1000) 表示立刻触发第一次, 然后接着每一秒再触发

timer(3000, 1000) 表示, delay 3 秒后触发第一次, 然后接着每一秒再触发

timer(1000) 如果没有提供 duration 参数, 那么它就是 delay 1 秒后触发, 然后就结束了. 这就相等于实现了 setTimeout

of

of 的目的是把 variable 转化成 Obserable

of(10).subscribe(value => console.log(value)); // 立马触发 console 10

它有啥用呢?

因为 RxJS 有一种万物皆是 Observable 的概念, 统一管理嘛.

许多 RxJS 的 operators 参数或返回值都必须是 Observable 类型, 所以 of 就可以到达 cast 类型的目的.

from

from 和 of 目的都是 cast, 但是 of 用于普通变量

from 用 Promise, Iterable

of([1, 2, 3]); // 触发 1 次, value = [1,2,3] Array
from([1, 2, 3]); // 触发 3 次, value = 1 Number

从上面这个例子可以看出它们的不同之处

不只是 array, from 可以处理任何 Iterable.

另外, from 还经常用于将 Promise 转成 Obserable

of(Promise.resolve(1)); // 得到的是 Promise 对象
from(Promise.resolve(1)); // 得到的是 Number 1

iif

iif 是 conditional create Observable 方法. 类似一个 Ternary operation 三元运算符

iif(() => true, of(1), of(2)).subscribe(v => console.log(v)); // 1

1 个参数是判断函数

2 是 true 情况下返回的 Observable

3 是 false 情况下返回的 Observable

每一次 subscribe 调用的时候, 判断函数都会被执行获取最新的判断值 (所以它是一个函数而不是一个固定的变量).

defer

defer 有延后执行的意思. 它通常拿来延后 of

of('variable') 会先把 variable 存起来, 当 subscribe 调用时立刻返回这个 variable

defer(() => of('can be a random variable')) 则不会把 variable 存起来, 每一次调用 subscrube 的时候, 会执行创建 observable 函数.

得到一个新的 Observable 然后订阅它.

Empty 和 throwError

Empty 是一个直接 complete 的 Observable,一旦 subscribe 它立马就发布 complete。

throwError 是一个直接 error 的 Observable,一旦 subscribe 它立马就发布 error。

EMPTY.subscribe({
next: () => console.log('next'), // won't call
complete: () => console.log('complete'), // direct complete 了
}); throwError(() => 'error message').subscribe({
error: message => console.log(message), // direct error 了
complete: () => console.log('complete'), // won't call
});

它们不用于创建一个 Observable 起点,通常用于 pipe 中 operator 的返回。

没有介绍到的 Creation Operators

ajax

bindCallback

bindNodeCallback

generate

range

一句话总结

fromEvent : 替代 addEventListener

fromEventPattern : 自定义 addEventListener 的方式

interval : setInterval

timer : interval 的扩展, 可以实现 setTimeout

of : Variable to Observable

from : convert Iterable and Promise

iif : conditional 三元 create Observable

defer : 延迟 of('variable')

empty : complete

throwError : error

RxJS 系列 – Observable & Creation Operators的更多相关文章

  1. RxSwift 系列(三) -- Combination Operators

    RxSwift 系列(三) -- Combination Operators 前言 本篇文章将要学习如何将多个Observables组合成一个Observable. Combination Opera ...

  2. Angular学习笔记—RxJS与Observable(转载)

    1. Observable与观察者模式的关系 其实这里讲的Observable就是一种观察者模式,只不过RxJS把Observable结合了迭代模式以及附件了很多的operator,让他变得很强大,也 ...

  3. [RxJS] Creation operators: interval and timer

    It is quite common to need an Observable that ticks periodically, for instance every second or every ...

  4. [RxJS] Creation operators: empty, never, throw

    This lesson introduces operators empty(), never(), and throw(), which despite being plain and void o ...

  5. [RxJS] Creation operators: fromEventPattern, fromEvent

    Besides converting arrays and promises to Observables, we can also convert other structures to Obser ...

  6. [RxJS] Creation operators: from, fromArray, fromPromise

    The of() operator essentially converted a list of arguments to an Observable. Since arrays are often ...

  7. [RxJS] Creating Observable From Scratch

    Get a better understanding of the RxJS Observable by implementing one that's similar from the ground ...

  8. [RxJS] Using Observable.create for fine-grained control

    Sometimes, the helper methods that RxJS ships with such as fromEvent, fromPromise etc don't always p ...

  9. ng-packagr 打包报错 Public property X of exported class has or is using name 'Observable' from external module “/rxjs/internal/Observable” but cannot be named

    old import { Injectable } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; @Injectable( ...

  10. RxSwift 系列(四) -- Transforming Operators

    前言 本篇文章将要学习RxSwift中四种转换操作符: map flatMap flatMapLatest scan map 通过使用一个闭包函数将原来的Observable序列转换为一个新的Obse ...

随机推荐

  1. oeasy教您玩转 linux 010213 中文 fcitx

     我们来回顾一下 上一部分我们都讲了什么? 管道 ls | cowsay 管道的符号是| 管道的作用是连接 原来应该输出到屏幕的内容 通过管道流到了另一个命令做为参数 这次是否可以让cow说出一些中文 ...

  2. mybatis源码配置文件解析之五:解析mappers标签(解析class属性)

    在上篇文章中分析了mybatis解析mapper标签中的resource.url属性的过程,<mybatis源码配置文件解析之五:解析mappers标签(解析XML映射文件)>.通过分析可 ...

  3. 一款.NET开源、跨平台的DASH/HLS/MSS下载工具

    前言 今天大姚给大家分享一款.NET开源(MIT License).免费.跨平台的DASH/HLS/MSS下载工具,并且支持点播和直播(DASH/HLS)的内容下载:N_m3u8DL-RE. 网络流媒 ...

  4. win10安装和使用wireshark

    win10安装和使用wiresharkhttps://blog.csdn.net/qq_34732729/article/details/105126146https://blog.csdn.net/ ...

  5. Python和RPA网页自动化-浏览器切换不同窗口页面

    以百度为例,点击[BOSS直聘]词条会打开一个BOSS新窗口页面,分别使用Python和RPA网页自动化在不同的窗口页面来回切换窗口完成以下顺序步骤 1.Python代码如下 步骤:打开新窗口页面后, ...

  6. vue pinia sessionstorage 数据存储不上的原因

    vue pinia sessionstorage 的坑 默认的配置是开始 localStorage 如果用 sessionstorage 则发现数据存储不上 ,是因为缺少了序列化和反序列化 impor ...

  7. 标准DQN在测试算法性能时为什么要将探索概率epsilon设置为0.05呢,而不是使用其他探索概率的epsilon-greedy策略或者直接使用greedy探索策略呢?

    标准dqn的策略网络参数更新所采用的规则为Q-learning中的更新规则,总所周知的是Q-learning是异策略算法,异策略算法就是行为策略和评估策略(更新所得策略)是不同的. 更新规则: q-l ...

  8. Ubuntu18.04 环境下 解决VScode中空格长度减小的问题

       问题:   Ubuntu下安装vscode后发现空格特别小,只有正常空格的一半大小 ======================================================= ...

  9. gym.wrappers.Monitor报错,无法使用

    使用gym中的录制功能,报错,具体: >>> import gym >>> gym.wrappers.MonitorTraceback (most recent c ...

  10. 【转载】 使用t-SNE可视化图像embedding

    原文地址: https://zhuanlan.zhihu.com/p/81400277 ================================================ t-SNE(t ...