前言

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. Django集成的密码找回功能

    要实现忘记密码功能,您需要进行以下修改: 添加忘记密码链接到登录页面. 创建密码丢失修改页面. 创建密码修改页面. 编写相应的视图函数来处理密码丢失修改和密码修改逻辑. 编写发送验证信息到邮箱的逻辑. ...

  2. 基于微信小程序的校园维修管理系统-开题报告参考

    \n文末获取源码联系 感兴趣的可以先收藏起来,大家在毕设选题,项目以及论文编写等相关问题都可以给我加好友咨询 一.课题研究的目的和意义** 本研究开发基于微信小程序的物品维修系统,它不仅能实现专业的维 ...

  3. ctfshow sql-labs(笔记)

    这是当时做题的时候记得笔记有些乱看不懂的可以私我 判断闭合方式: id=1' and 1=1–+ *正常回显* id=1' and 1=2–+ *异常回显* id=1 and 1=1 *正常回显* i ...

  4. 文件系统(十):一文看懂 UBI 文件系统

    liwen01 2024.07.21 前言 UBI (Unsorted Block Images)文件系统是一种用于裸 flash 的文件系统管理层.它是专为管理原始闪存设备而设计,特别适用于嵌入式系 ...

  5. Aic 应用开发基础一(概念与场景)

    Agi通用人工智能应用概念与场景 大家看到,自美国OpenAI主导的GPT发布以来,全球科技领域掀起了革命性的浪潮.比如最近看到的aigc 人工绘图,智能机器人等行业,很多新概念掘起, 随着人工智能技 ...

  6. 【Java】SonarLint 疑难语法修正

    规范驼峰命名使用: 提示信息 Local variable and method parameter names should comply with a naming convention 代码片段 ...

  7. 【C3】04 工作原理

    我们已经知道了CSS是做什么的以及怎么写简单的样式这样基础的CSS, 接下来我将了解到浏览器如何获取CSS.HTML和将他们加载成网页. 前置知识: 基础计算机知识.基本软件安装.简单文件知识.HTM ...

  8. AI领域的国产显卡如何在现有技术下吸引用户 —— 廉价增加显存 —— 大显存

    先给出一个不大准确的但相差不差的背景介绍: 同样性能级别的显卡,NVIDA的24G的要3W,32G的要5W,48G的要7W, 80G的要10W. 国产同同性能的显卡32G的要10W,48G的要15W, ...

  9. SemanticKernel/C#:实现接口,接入本地嵌入模型

    前言 本文通过Codeblaze.SemanticKernel这个项目,学习如何实现ITextEmbeddingGenerationService接口,接入本地嵌入模型. 项目地址:https://g ...

  10. 秒懂全文:盘点13个各具特色的AI智能阅读助手工具

    在当今信息爆炸的时代,AI阅读工具正在革新我们的阅读方式,成为了提高效率.优化阅读体验的关键. 这类AI阅读辅助工具,只需要上传文件或者输入链接,便可以直接以聊天对话的形式进行一键总结和智能问答,满足 ...