深入浅出Rxjs核心概念

深入浅出Rxjs

1. RxJS 是什么?

RxJS 是用于 JavaScript 的 ReactiveX 库,RxJS 是 Observables 的响应式编程库,它使编写异步或基于回调的代码更容易

从这个官方描述来看,功能很明显,使得编写异步或者基于回调的代码更容易。
既然是方便编写异步代码的,没有 rxjs 的异步代码是什么样的呢?


2. 编写异步或回调的代码

异步编程是相对于同步的编程方式,下面就用一个小例子,来介绍几种异步编码方式。
例子功能描述:当前有一个 names.txt 文件,该文件中存放了下一个要打开的文件的文件名(a.txt),a.txt 中又存放了最终想要的文件名,该文件名中存放数据。
目录结构如下:

test.js     //脚本程序
names.txt  // 内容 a.txt
/file
    a.txt   //内容 b.txt
    b.txt   //内容 终于等到你!

目录

2.1 基本的异步回调的代码

const fs = require("fs");
const path = require("path");

let filePath = path.resolve(__dirname, "names.txt");

fs.readFile(filePath, (err, data) => {
  if (err) console.log(err);
  else {
    let firstFile = data.toString();
    fs.readFile(path.resolve(__dirname, "./file", firstFile), (err, data) => {
      if (err) console.log(err);
      else {
        let secondFile = data.toString();
        fs.readFile(
          path.resolve(__dirname, "./file", secondFile),
          (err, data) => {
            if (err) console.log(err);
            else {
              console.log(data.toString());
            }
          }
        );
      }
    });
  }
});

可以看到,对于连续依赖的异步请求,代码变得非常难读并且需要在每一中间过程进行错误处理。如果嵌套过深,就会出现回调地狱的金字塔结构,并且难以调试和定位问题。

2.2 使用 Promise 来处理回调

ES6 提供 promise 来处理异步逻辑,可以避免嵌套回调地狱问题。

const fs = require("fs");
const path = require("path");

let filePath = path.resolve(__dirname, "names.txt");

let readFilePromise = function(fileName) {
  return new Promise((resolve, reject) => {
    fs.readFile(fileName, (err, data) => {
      if (err) reject(err);
      else resolve(data.toString());
    });
  });
};

readFilePromise(filePath)
  .then(firstFile =>
    readFilePromise(path.resolve(__dirname, "./file", firstFile))
  )
  .then(secondFile =>
    readFilePromise(path.resolve(__dirname, "./file", secondFile))
  )
  .then(finalData => console.log(finalData))
  .catch(err => console.log(err));

Promise 的then(),隐含着调用的时序,并且可以在一个 catch 中捕获错误,相比于 2.1 中的写法,好的很多。
但是呢,Promise 也有一些缺陷:

  1. Promise 无法处理数据源发出的不只一个值的情况,例如鼠标的移动或者文件中的字节流序列。
  2. Promise 也无法实现错误重试
  3. 最重要的是,Promise 无法取消,Promise 是不可变的。

2.3 RxJS 的解决方案

上面用 Rxjs 的方案来解决,代码如下:

const fs = require("fs");
const path = require("path");
const Rx = require("rxjs/Rx");
let filePath = path.resolve(__dirname, "names.txt");

let readFilePromise = function(fileName) {
  return new Promise((resolve, reject) => {
    fs.readFile(fileName, (err, data) => {
      if (err) reject(err);
      else resolve(data.toString());
    });
  });
};

Rx.Observable.fromPromise(readFilePromise(filePath))
  .switchMap(firstFile =>
    Rx.Observable.fromPromise(
      readFilePromise(path.resolve(__dirname, "./file", firstFile))
    )
  )
  .switchMap(secondFile =>
    Rx.Observable.fromPromise(
      readFilePromise(path.resolve(__dirname, "./file", secondFile))
    )
  )
  .subscribe({
    next: console.log,
    error: err => console.log(err),
    complete: _ => {}
  });

貌似看起来和复杂,其实这个实例发挥不出 Rxjs 强大的效果,只是做个展示,Rxjs 能解决这个问题。
Rxjs 的哲学是函数式与响应式相结合。下面来介绍 Rxjs 的一些核心概念。


3. RxJS 的一些概念

3.1 一些预备知识

RxJS 中的用到了一些设计模式的概念,主要是观察者模式和迭代器模式。先介绍这两种设计模式:

3.1.1 观察者模式

为一个软件开发者,提到 Observable,肯定会联想到观察者模式。
在观察者模式中,有一个称为 Producer(生产者)的对象,该对象维持一个所有订阅它的 listeners 列表。当调用 update 方法时,所有的 listeners 都会接受到通知。在大部分观察者模式的解释中,整个对象称为 Subject(主体)。但是为了区别于 RxJS 自己的 Subject 类型,就称它为 Producer。

下面是观察者模式的简单实现:

function Producer() {
  this.listeners = [];
}

Producer.prototype.add = function(listener) {
  this.listeners.push(listener);
};

Producer.prototype.remove = function(listener) {
  var index = this.listeners.indexOf(listener);
  this.listeners.splice(index, 1);
};

Producer.prototype.notify = function(message) {
  this.listeners.forEach(function(listener) {
    listener.update(message);
  });
};

var listener1 = {
  update: function(message) {
    console.log("Listener 1 received:", message);
  }
};

var listener2 = {
  update: function(message) {
    console.log("Listener 2 received:", message);
  }
};

var notifier = new Producer();
notifier.add(listener1);
notifier.add(listener2);
notifier.notify("Hello there!");
3.1.2 迭代器模式

迭代器是一个对象,用来提供给 consumer(消费者)一种遍历它内容的简便方式,这就可以从 consumer 中隐藏具体遍历的实现。

迭代器模式接口非常简单。只需要两个方法:next()方法获得序列中的下一个值,hasNext()方法检查是否还有值在序列中。
下面是迭代器模式的实现:

function iterateOnMultiples(arr, divisor) {
  this.cursor = 0;
  this.array = arr;
  this.divisor = divisor || 1;
}

iterateOnMultiples.prototype.next = function() {
  while (this.cursor < this.array.length) {
    var value = this.array[this.cursor++];
    if (value % this.divisor === 0) {
      return value;
    }
  }
};

iterateOnMultiples.prototype.hasNext = function() {
  var cur = this.cursor;
  while (cur < this.array.length) {
    if (this.array[cur++] % this.divisor === 0) {
      return true;
    }
  }
  return false;
};

var consumer = new iterateOnMultiples([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 3);
console.log(consumer.next(), consumer.hasNext());
console.log(consumer.next(), consumer.hasNext());
console.log(consumer.next(), consumer.hasNext());

3.2 流

RxJS 中的流,可以理解为任何具有值的数据点。例如,可以是整数字节、可以是网络请求等。
为了方便理解,我们使用伪代码Stream来表示一个流。
假设流中带有值为 42 的数据,那么可以用伪代码表示如下:

Stream(42);

在 Rxjs 中,这个流意味着携带 42 的数据,但是什么都没有发生。当有一个 subscriber(或者称 observer)订阅了这个流,这个值 42 才会发射出来。

Stream(42).subscribe(val => console.log(val)); //subscribe后才会输出42

这点与 Promise 不同.

new Promise((resolve, reject) => {
  resolve(42);
});

Promise 的 42,在创建的时候,就会发射 42。状态已经变为fulfilled,但是在 Rxjs 中,创建 Stream(42)并不会发射出 42,只在 subscribe()订阅后,才会发射出 42。
这就是响应式的一种体现。

流跟消费者之间形成了一个管道,可以在管道中操纵数据。

Stream([1, 2, 3, 4, 5])
  .filter(num => num % 2 === 0)
  .map(num => num * num)
  .subscribe(val => {
    console.log(val);
  });

在这里插入图片描述

3.3 Producers

Producers(生产者)是数据源。流必须拥有数据的产生者。数据源可以是任何类型的数据,可以是单一值、数组、鼠标点击,或者文件的字节流。
观察者模式定义 Producers 为 subject,但是为了区别于 RxJS 自己的 Subject 类型,就称它为 observables,意思是 something that’s able to be observed。
Observable 是负责推送消息,即触发并发射出值,但是并不进行值的处理。

3.4 Consumers

有了 Producers,必须要有 consumer 来接收 producers 给的值,然后以一种特定的方式处理。当 consumer 开始监听 producer 生产的值,这就形成了一个流(stream)。并且这也是流推送值的开始点。对于 Consumer,也有一个别名称为 observer。

流只从 producer 传递给 consumer,这个方向不可改变。

3.5 Data Pipeline

Rxjs 的一个核心优势在于,它可以操纵或者改变从 producers 传递给 sonsumer 的数据。这个过程中,Rxjs 提供了各种操作符来完成不同的功能。

3.6 time

时间是 Rxjs 中隐含的内容,流可以变快变慢,是个有序的过程。


4. 创建 Observable

创建数据流的操作符很多,这里只提几个常用的。

  1. create

创建自定义的 observable,当观察者 Observer 订阅该 Observable 时,会执行指定的函数。
create 函数参数是一个函数 onSubscription。返回一个 Observable。该 Observable 会在 subscribe 订阅的时候,运行 onSubscription 函数。 onSubscription 函数接受一个 observer 对象,带值调用 next 会将该值发出给观察者。调用 complete 意味着该 Observable 结束了发出并且不会做任何事情了。 调用 error 意味着出现了错误,传给 error 的参数应该提供详细的错误信息。。

var observable = Rx.Observable.create(function(observer) {
  observer.next(1);
  observer.next(2);
  observer.next(3);
  observer.complete();
});
observable.subscribe(
  value => console.log(value),
  err => {},
  () => console.log("this is the end")
);
  1. fromPromise

可以将 Promise 作为 RxJS 的 observable。

const computeFutureValue = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(42);
  }, 3000);
});
Rx.Observable.fromPromise(computeFutureValue).subscribe(
  val => {
    console.log(val);
  },
  err => {
    console.log(`Error occurred: ${err}`);
  },
  () => {
    console.log("All done!");
  }
);
  1. fromEvent

可以是 Dom 事件或者 node 的 eventEmitter 事件

const Rx = require("rxjs/Rx");
const EventEmitter = require("events");
const emitter = new EventEmitter();
const source$ = Rx.Observable.fromEvent(emitter, "msg");

source$.subscribe(console.log, error => console.log("error", error), () => {});

emitter.emit("msg", 1);
emitter.emit("msg", 2);
emitter.emit("msg", 3);
emitter.emit("msg", 4);
  1. from、of、interval、range

这些常用操作符,看相关 api 文件即可,非常容易理解。
RxJS文档

5. 使用 operator 操作流

Rxjs 具有函数式编程的思想,所以 Rxjs 的操作符大多数都是与函数式编程类似。
对 Observable 对象能够链式调用一些操作符,如 filter、map 等,原因是

  1. filter 和 map 都是 Observable 对象的成员函数
  2. filter 和 map 的返回结果都是 Observable 对象
  3. filter 和 map 不会改变原有的 Observale 对象

RxJS 的操作符都遵循函数式编程的思想。

5.1 操作符分类

操作符按照功能分类,大概有以下几个内容:

  1. 创建,即创建 Observable 的操作符,如 from、of、interval
  2. 转换 如 map、pluck
  3. 过滤 如 filter、last、take、throttle、debounce
  4. 合并 如 concat、merge、zip
  5. 多播 如 multicast
  6. 错误处理 如 catch、retry
  7. 辅助工具 如 do
  8. 条件分支 如 every、find、isEmpty
  9. 数学 如 count、max、min、reduce

5.2 弹珠图(marble diagram)——操作符的可视化功能理解

弹珠图(其实应该叫“大理石”图?),是一种理解 Rxjs 数据流的一种辅助工具,能够帮助我们厘清操作符的含义。
下面通过这个例子来说明弹珠图

Rx.Observable.of(4,6,'a',8)
  .map(multiplyByTen)
  .subscribe(console.log);

弹珠图

首先,从图中可以看出几个基本组成要素:

  1. x 轴:是时间轴,代表产生的时间顺序
  2. 圆:圆是代表一个值,Observable 通过调用 next(value)方法,发出的 value 值
  3. 竖线:在时间轴左侧代表开始,后侧代表 complete 结束
  4. 中间椭圆矩形框,代表 operator 的处理过程,返回的是新的 observable 数据流

5.3 实现操作符

Rxjs 已经提供了操作符 filter,用来出 true 的数据。我们现在如果想要增加一个 filter 的反义词 exclude,这个操作符如何实现呢?

function exclude(predicate) {
  return Rx.Observable.create(subscriber => {
    let source = this; //箭头函数的this指向的定义上下文的this

    //Observable内部逻辑如下
    return source.subscribe(
      value => {
        //next消费原observable的数据
        try {
          if (!predicate(value)) {
            //对原observable的数据进行处理,满足处理结果的数据传给现observable的next供消费
            subscriber.next(value);
          }
        } catch (err) {
          subscriber.error(err); //出错走error
        }
      },
      err => subscriber.error(err), // 传递原error
      () => subscriber.complete() //原complete方法
    );
  });
}

Rx.Observable.prototype.exclude = exclude; //绑定到Observable原型上

6. 取消流

Observable 通过 subscribe()方法订阅后,会返回一个 Subscription 对象,该对象的 unsubscribe()方法,可以取消流,并清理流所占资源。调用 unsubscribe 方法后,即停止流,结束整个过程。

7. 实现一个 tiny Rxjs

Rxjs 的核心就是一个 observable 的产生、流动、和消费。这些个过程都是通过 observable 来完成。简单的过程就是,创建一个 observable、订阅 subscribe 产生数据流,订阅后的结果流提供 unsubscribe()方法终止。
简单的实现即:

class TinyObservable {
  IntervalOf(events) {
    const INTERVAL = 1 * 1000;
    let schedulerId;
    return {
      subscribe: observer => {
        schedulerId = setInterval(() => {
          try {
            if (events.length == 0) {
              observer.complete();
              clearInterval(schedulerId);
              schedulerId = undefined;
            } else {
              observer.next(events.shift());
            }
          } catch (err) {
            observer.error(err);
            clearInterval(schedulerId);
            schedulerId = undefined;
          }
        }, INTERVAL);

        return {
          unsubscribe: () => {
            if (schedulerId) {
              clearInterval(schedulerId);
            }
          }
        };
      }
    };
  }
}

var Observable = new TinyObservable();
let subscription = Observable.IntervalOf([1, 2, 3]).subscribe({
  next: console.log,
  error: err => console.log("error", err),
  complete: () => console.log("Done")
});
subscription.unsubscribe();

Observable 上的操作符,即参照上面实现的 exclude,添加至原型上即可。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值