RxJS —— 异步数据流利器

RxJS 适用于异步场景,即前端交互中接口请求、浏览器事件以及自定义事件。RxJS 统一了异步编程的规范,将Promise、ajax、浏览器事件,通通封装成序列

如果是学过信号处理的同学,可以将 RxJS 中的函数(例如filter, debounce, delay)想象成一个个滤波器,将ajax、事件等看作信号输入。一个 Observable 对向就是信号源。

RxJS 与 Promise

Promise 每次都时用 then 来处理上一次返回的结果

Rxjs 则有丰富的库函数供我们使用,最常用的是 map 函数,效果与 then 类似。即获取一个值,返回一个值。

不同点是 then 中的函数一定是异步执行的,而 map 中则是同步的。

let log = console.log.bind(console);

let promise = Promise.resolve(0);
promise
    .then(x => x + 1)
    .then(log);       // 1

let source = Rx.Observable.of(0);
source
    .map(x => x + 2)
    .subscribe(log);  // 2

需要注意的是在 Promise 中,如果返回一个 Promise 对象, 后面 then 中得到是 Promise 里面包含的值。

而在 RxJS 中如果返回一个 Observable 对象,可以认为是创建了一个新的数据源,直接使用 map 得到是 Observable 对象,而不是里面的值。如果想要得到里面的值,需要使用 flapMap 函数。

flapMap 直观的解释是拍平数据,也就是从 Observable 这个容器中拿出里面包含的值。

let promise = Promise.resolve(0);
promise
    .then(x => Promise.resolve(x + 1))
    .then(log);

let source = Rx.Observable.of(0);
source
    .flatMap(x => Rx.Observable.of(x + 2))
    .subscribe(log);

RxJS 优点

RxJS 擅长处理异步数据流,而且具有丰富的库函数

下面用一个搜索自动补全的例子来看 RxJS 强大的功能。

let input = document.querySelector('#input');

//return some ajax data
let ajaxSearch = function (input) {
    //mock data
    return Promise.resolve(`search data: ${input}`);
};

let source = Rx.Observable.fromEvent(input, 'keyup');
source
    .map(event => event.target.value)
    .debounce(500)
    .distinctUntilChanged()
    .filter(input => {
        input = input.replace(/\s+/, "");
        return input !== '';
    })
    .flatMapLatest(ajaxSearch)
    .subscribe(log);
  • 首先将 Input 的 keyup 作为事件源
  • 使用 map 获得输入的值
  • 使用 debounce 去抖,即500ms获取一个值,防止频繁发送请求
  • 使用 distinctUntilChanged 去掉重复的值,防止搜索重复的内容
  • 使用 filter 滤除空字符串
  • 使用 flatMapLatest 获取请求中的内容,flatMapLatest 用法与 flatMap 类似,但是如果上一次的请求还未返回,又发出一个新请求,则忽略上一个请求

下面是普通的写法,需要定义很多中间局部变量。可以看到 RxJS 异步数据流更加清晰简洁

let timer;
let preValue;
let search = event => {
    if (timer) clearTimeout(timer);

    let input = event.target.value;
    timer = setTimeout(() => {
        input = input.replace(/\s+/, "");
        if (input === '' || input === preValue) {
            return;
        }
        preValue = input;
        ajaxSearch(input).then(log);
    }, 500);
};

input.addEventListener('keyup', search);

subject

Subject 是一类特殊的 Observable,不仅可以订阅,也可以发射值。

let subjet = new Rx.Subject();
subjet.subscribe(log);

subjet.onNext(1); // => 1
subjet.onNext(2); // => 2

cold and hot observables

cold observables 只有早订阅 (调用 subscribe) 后才会执行,且不同的订阅者之间也不会共享。在下面的例子中,首先创建一个500ms的定时器,并且取3个值。第二个订阅者延迟1s订阅,但还是能够获得所有的值,即不同订阅者之间没有共享数据。

let source = Rx.Observable.interval(500).take(3);

let subscription1 = source.subscribe(x => console.log(`a: ${x}`));

setTimeout(() => {
    let subscription2 = source.subscribe(x => console.log(`b: ${x}`))
}, 1000);

// => a: 0
// => a: 1
// => a: 2
// => b: 0
// => b: 1
// => b: 2

hot observables 不同之处在于,即使没有被订阅,也会产生值。所有的订阅者共享发射的数据。Subject 就属于 hot observables。在下面的例子中,当 subject 发射 0 时,因为还没有被订阅,所以即使后面订阅了也不能获得这个值。

let subjet = new Rx.Subject();

subjet.onNext(0);
let subscription1 = subjet.subscribe(x => console.log(`a: ${x}`));
subjet.onNext(1);
let subscription2 = subjet.subscribe(x => console.log(`b: ${x}`));
subjet.onNext(2);

// => a: 1
// => a: 2
// => b: 2

错误处理

RxJS 中抛出的错误可以用 catch 捕捉后返回一个正常的值(或默认值),也可以在最后订阅的时候处理,只需将错误处理函数作为 subscribe 的第二个参数即可。

let source = Rx.Observable.of(0);
source
    .map(x => {
        throw 'got an error'
    })
    .subscribe(
        log,
        err => console.error(err)
    );
// => got an error

source
    .map(x => {
        throw 'got an error'
    })
    .catch(err => {
        return Rx.Observable.of('error has been handled')
    })
    .subscribe(
        log,
        err => console.error(err)
    )
// => error has been handled

RxJS 中还提供了一个 Rx.Observable.throw 方法来抛出错误。需要注意的是这个方法一般与 flapMap 配合使用,因为错误被包裹在 Observable 里面了。

source.flatMap(x => {
    return Rx.Observable.throw('got an error');
})

当数据流中发生一个错误时,就会终止当前的流,在下面的例子中,如果输入了 ‘x’,后面就不会再对 keyup 事件进行监听了,因为当前流已经被终止了。

let input = document.querySelector('#input');
let source = Rx.Observable.fromEvent(input, 'keyup');
source
    .map(event => event.target.value)
    .flatMap(x => {
        if (x === 'x') {
            return Rx.Observable.throw('got x error');
        }
        return Rx.Observable.of(`input: ${x}`);
    })
    .subscribe(
        log,
        e => console.error(e)
)

下面是两种捕获错误的方式,1是直接在可能发生错误的地方捕获,另一个是在 flatMap 后捕获。方法1在捕获错误后不再对 keyup 事件进行监听,但方法2继续监听。

因为在一个数据流中发生错误后就会被终止,如果数据源 a 抛出了错误没有处理,然后错误被合并到数据源 b 中,那个数据源 b 也会被终止。而如果在数据源 a 中就已经处理了在错误,那么合并到数据源 b 时是一个合法的值,因此数据源 b 不会被终止。

// 方法1
source
    .map(event => event.target.value)
    .flatMap(x => {
        if (x === 'x') {
            return Rx.Observable.throw('got x error');
        }
        return Rx.Observable.of(`input: ${x}`);
    })
    //catch after flatMap
    .catch(e=>{
        return Rx.Observable.of('error has been handled');
    })
    .subscribe(
        log,
        e => console.error(e)
)
// 方法2
source
    .map(event => event.target.value)
    .flatMap(x => {
        if (x === 'x') {
            return Rx.Observable.throw('got x error')
                // catch before flatMap
                .catch(e => {
                    return Rx.Observable.of('error has been handled');
                })
        }
        return Rx.Observable.of(`input: ${x}`);
    })

    .subscribe(
        log,
        e => console.error(e)
    )

from

创建 Observable 的方式有多种

// 自定义
var source0 = Rx.Observable.create(observer => {
  observer.onNext(0);
  observer.onNext(1);
  observer.onCompleted();
});

// 数组(Iterables, 也可以是 Map Set Generators)作为数据流
var source = Rx.Observable.from([1,2,3]);

// Promise 转化为 Observalble
let promise = Promise.resolve(0);
let source = Rx.Observable.fromPromise(promise);

//从callback转化
let callback=(i,fn)=>fn(i+1);
let addOne = Rx.Observable.fromCallback(callback);
addOne(0).subscribe(log); // => 1

参考资料

https://github.com/Reactive-Extensions/RxJS

https://gist.github.com/staltz/868e7e9bc2a7b8c1f754

https://xgrommx.github.io/rx-book/index.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值