(一)温故知新系列之RXJS——RXJS相关概念

864 阅读5分钟

前言

  • RxJS的学习曲线太陡峭,分别前后两次学习 RxJs ,还是感觉似懂非懂,创作此系列的目的就是为了重新在学一次 RxJs 征服它 。

  • Angular 是目前的前端框架中最重度使用rxjs的,不过 rxjs 和 Angular 关系不大,它可以在任何前端框架中使用,本系列均采用rxjs V7 版本。

一、什么是RxJS?

RxJs 是一个工具库,它通过使用 observable 序列来编写异步和基于事件的程序。在 RXJs 的官网中有一句话——可以把 RxJS 当做是用来处理事件的 Lodash 。 没错 RxJs 内部就是封装了一系列的方法,可以对数据来进行操作,数组在Js中我们可以使用相关的方法去操作他,而在RxJs中我们操作的都是 Observable ,学习 RxJs 之前需要了解几个概念性的东西,下面来逐个介绍。

先来一个小例子,看一下 Rx 编程和我们传统方式编程有什么区别。

// 传统编程方式
const text = document.querySelector("#text");
const throttle = _.throttle((e) => console.log( `X${e.x}--Y${e.y}`), 2000, { 'trailing': false });
document.addEventListener("click", (e) => {
     throttle(e)
 })
// Rx 编程
 const document$ = rxjs.fromEvent(document, "click")
 document$.pipe(
     throttleTime(2000),
     map( e => ({x:e.x,y: e.y})),
 ).subscribe( ( {x,y} ) => {
     console.log( `RXJS: X${x}--Y${y}`)
 })

上面是一个点击时,获取鼠标当前位置的一个小 demo 只不过要 2000ms 才输出一次 , 这个例子其实没有特别体现出 rxjs 相比传统编程的优势, 目的只是演示一下一个最基本的 rxjs 写法。

二. Observable(可观察对象)

RxJS 的核心,也是最基础的类, Observable 就是“可以被观察的对象”即“可被观察者”,⽽ Observer 就是“观察者”,连接两者的桥梁就是 Observable 对象的函数 subscribe 。
RxJS 中的数据流就是 Observable 对象,Observable 实现了两种设计模式观察者模式迭代器模式 ,这两种设计模式也无形中提高了 Rx 的学习难度。

创建Observable

Observable 对象就像⼀台只会 产⽣数据的机器,Observer则是⼀台只会处理数据的机器,不把这两台机 器连接起来,什么都不会发⽣,Observable 的逻辑和 Observer 的逻辑都不会 执⾏,只有两者通过subscribe 连接起来之后,才能让这两台机器运转起来。

 const { Observable } = rxjs;
 const onSubscribe = observer => {
     observer.next(1);
     observer.next(2);
     observer.next(3);
 };
 const source$ = new Observable(onSubscribe);
 const theObserver = {
    next: item => console.log(item)
 }
 source$.subscribe(theObserver)

让我们来分析这段代码:

(1).我们导⼊了 Observable 类。
(2).创造⼀个函数 onSubscribe,这个函数会被⽤作Observable构造 函数的参数,这个函数参数完全决定了Observable对象的⾏为。onSubscribe 函数接受⼀个名为observer的参数,函数体内,调⽤参数observer的next函 数,把数据“推”给observer。
(3).调⽤Observable构造函数,产⽣⼀个名为 source$ 的数据流对象。
(4).创造观察者 theObserver。
(5).通过subscribe函数将theObserver和source$关联起来。

创建Observable对象也就是创建⼀个“发布者”,⼀个“观察者”调⽤某个 Observable对象的subscribe函数,对应的onSubscribe函数就会被调⽤,参数 就是“观察者”对象,onSubscribe函数中可以任意操作“观察者”对象。这个 过程,就等于在这个Observable对象上挂了号,以后当这个Observable对象 产⽣数据时,观察者就会获得通知。

在上⾯的代码中,“观察者”就是theObserver。

让我们再走一遍上面的代码.更能了解Observable和Observer的交互过程

(1)定义了onSubscribe函数,这个函数作为参数传递给Observable构造函数,创建了⼀个对象source$,这时候,onSubscribe并没有被调⽤,它只 是在等待 source$ 的subscribe被调⽤。

(2)theObserver对象被产⽣了,这时候onSubscribe依然没有被调⽤,因 为到⽬前为⽌,source$ 和theObserver还毫⽆关系。

(3)调⽤source$.subscribe,theObserver成为了source$的“观察者”。就 在subscribe函数被调⽤的过程中,onSubscribe被调⽤,这时候, onSubscribe函数的参数observer所代表的就是观察者theObserver,但并不是 theObserver对象本⾝,RxJS会对观察者做⼀个包装,在这⾥,observer对象 实际上是theObserver的⼀个包装,所以⼆者并不完全⼀样,可以把observer 理解为观察者的⼀个代理,对observer的所有函数调⽤都会转移到 theObserver的同名函数上去。

(4)在onSubscribe函数中,连续调⽤observer.next函数三次,实际上也 就是调⽤了theObserver的next函数三次,⽽theObserver的next函数所做的就 是把传⼊参数通过console.log输出,所以,最后产⽣了连续的三个正整数 输出。 从上⾯的程序执⾏过程可以看出,单独⼀个Observable对象,或者单独⼀个Observer对象,都完成不了什么任务。Observable对象就像⼀台只会 产⽣数据的机器,Observer则是⼀台只会处理数据的机器,不把这两台机 器连接起来,什么都不会发⽣,Observable的逻辑和Observer的逻辑都不会 执⾏,只有两者通过subscribe连接起来之后,才能让这两台机器运转起来。

完结Observable

调⽤Observer的next只能表达“这是现在要推送的数据”,next没法表 达“已经没有更多数据了”,所以,为了让Observable有机会告诉 Observer“已经没有更多数据了”,需要有另外⼀种通信机制,在RxJS中, 实现这种通信机制⽤的就是Observer的complete函数。

const onSubscribe = observer => {
    let number = 1;
    const handle = setInterval(() => {
    observer.next(number++);
    if (number > 3) {
    clearInterval(handle);
        observer.complete();
        }
    }, 1000);
};
// 输出 1  2

如上⾯的代码可见,Observer对象的complete何时被调⽤,完全看 Observable的⾏为,如果Observable不主动调⽤complete,Observer即使准备 好了complete函数,也不会发⽣任何事情,和数据⼀样,完结信号也是由 Observable“推”给Observer的

异常Observable

Observable和Observer的交流,除了⽤next传递数据,⽤complete表 ⽰“没有更多数据”,还需要⼀种表⽰“出错了”的⽅式。 理想情况下,Observable只管⽣产数据给Observer来消耗,但是,难免有时候Observable会遇到了异常情况,⽽且这种异常情况不是Observable⾃ ⼰能够处理并恢复正常的,Observable在这时候没法再正常⼯作了,就需 要通知对应的Observer发⽣了这个异常情况,如果只是简单地调⽤ complete,Observer只会知道“没有更多数据”,却不知道没有更多数据的原 因是因为遭遇了异常,所以,我们还要在Observable和Observer的交流渠道 中增加⼀个新的函数error。

import {Observable} from 'rxjs/Observable';
const onSubscribe = observer => {
observer.next(1);
observer.error('Someting Wrong');
observer.complete();
};
const source$ = new Observable(onSubscribe);
const theObserver = {
    next: item => console.log(item),
    error: err => console.log(err),
    complete: () => console.log('No More Data'),
}
source$.subscribe(theObserver);

在RxJS中,⼀个Observable对象只有⼀种终结状态,要么是完结 (complete),要么是出错(error),⼀旦进⼊出错状态,这个Observable 对象也就终结了,再不会调⽤对应Observer的next函数,也不会再调⽤ Observer的complete函数;同样,如果⼀个Observable对象进⼊了完结状 态,也不能再调⽤Observer的next和error。

onSubscribe的参数observer并不是传给subscribe的 参数theObserver,⽽是对theObserver的包装,所以,即使在observer.error被 调⽤之后强⾏调⽤observer.complete,也不会真正调⽤到theObserver的 complete函数。

退订Observable

现在已经了解了Observable和Observer之间如何建⽴关系,两者之间除 了要通过subscribe建⽴关系,其实, onSubscribe函数可以返回⼀个对象,对象上可以有⼀个unsubscribe函数, 顾名思义,代表的就是和subscribe“订阅”相反的动作,也就是退订。

const source$ = new Observable(onSubscribe);
const subscription = source$.subscribe(item => console.log(item));
setTimeout(() => {
      subscription.unsubscribe();
}, 3500);

虽然unsubscribe函数调⽤之后,作 为Observer不再接受到被推送的数据,但是作为Observable的source$并没有 终结,因为始终没有调⽤complete,只不过它再也不会调⽤next函数了。

const onSubscribe = observer => {
    let number = 1;
        const handle = setInterval(() => {
        console.log('in onSubscribe ', number);
          observer.next(number++);
        }, 1000);
        return {
        unsubscribe: () => {
        //clearInterval(handle);
        }
    };
};  

上⾯只是程序运⾏前⼏秒钟的结果,如果不终⽌程序,会持续运⾏, 每隔⼀秒钟输出⼀⾏“in onSubscribe X”,其中X会是递增的正整数,⽽ Observer的输出在3秒钟之后就不再产⽣了。由此可见,Observable对象 source$在unsubscribe函数调⽤之后依然在不断调⽤next函数,但是, unsubscribe已经断开了source$对象和Observer的连接,所以,之后⽆论 onSubscribe中如何调⽤next,Observer都不会作出任何响应。 这是RxJS中很重要的⼀点:Observable产⽣的事件,只有Observer通过 subscribe订阅之后才会收到,在unsubscribe之后就不会再收到。

总结

  • Observable (可观察对象):  表示一个概念,这个概念是一个可调用的未来值或事件的集合。
  • Observer (观察者):  一个回调函数的集合,它知道如何去监听由 Observable 提供的值。
  • Subscription (订阅):  表示 Observable 的执行,主要用于取消 Observable 的执行。

Observable和Observer是RxJS的两⼤主⾓,这两者的关系是观察者模式 和迭代器模式的结合,通过Observable对象的subscribe函数,可以让⼀个 Observer对象订阅某个Observable对象的推送内容,可以通过unsubsribe函 数退订内容。

在Observable和Observer两者的关系⾥,Observer处于被动地位,代码 中可能并看不到⼀个Observer对象,⽽只看到分别代表next、error和 complete的函数。

Observable对象可以看作⼀个数据集合,但这个数据集合可以不是⼀ 次产⽣,⽽是在⼀个时间段上逐个产⽣每个数据,因为这个特性,⼀个 Observable对象即使产⽣超庞⼤的数据,依然不会消耗很多内存,因为每 次只产⽣⼀个,吐出来之后再产⽣另⼀个,不会积压。

参考《深入浅出RxJs》——程墨