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