RXJS详解

前言:

最近在学习 AnguarJS2 ,而 RXJS 在其中的异步操作里用得十分多,索性去网上找了找教程,却发现读起来晦涩难懂,后面绞尽脑汁才搞明白这个球。为了让大家充分了解到 RXJS 的作用,我们从最基础的异步回调讲起,然后再从  Promise过渡到 RXJS。


异步回调:

在我们平时编程中,当需要解决异步操作时,用得最多的应该就是把回调函数当做参数传递给异步函数了吧,如例1:
function print_msg(msg) {
	//Do something with msg
	console.log(msg);
}

function async_read(callback) {
	// Some async_work start
	// Async get data from a PORT into msg
	// Some async_work end
	callback(msg);
}

async_read(print_msg);
在例1中,我们通过传递 print_msg 这个回调函数给异步操作 async_read 以达到当异步操作完成时输出从端口读到的 msg 这个目的。
这样看起来,这种方式简单易懂,但是,真的很好用吗?想象一下,我们所传递的回调函数也是一个异步操作,也需要传入一个回调函数来处理异步结果,如例子2:
function my_console(value2) {
	//Do something with value2
	console.log(value2);
}

function async_work1(callback1, callback2) {
	// some async_work start
	//......get value1
	// some async_work end
	callback1(value1, callback2);
}

function async_work2(value1, callback2) {
	// some async_work start and do something with value1
	//......get value2
	// some async_work end
	callback2(value2);
}

async_work1(async_work2, my_console);
在例2中,我们将回调函数1(异步操作2)和回调函数2传给了异步操作1,以便在异步操作1完成时调用回调函数1(异步操作2),再将异步操作1得到的 value1 与回调函数2传给回调函数1(异步操作2),最终当回调函数1(异步操作2)完成后将调用回调函数2输出异步操作2得到的 value2 。当回调函数2也是异步操作的时候怎么办?难道真的这样一层套一层?这样的代码读起来晦涩易懂,我们要通过某种方式将其变得更简单,于是 Promise 出现了!


Promise:

Promise 很好的将这种嵌套式调用转变成了链式调用,使得代码的可读性维护性都更高。对于例1,我们可以这样:
var promise = new Promise(function(resolve, reject) {
	// Some async_work start
	// Async get data from a PORT into msg
	// Some async_work end
	if (/* Async_work successed */) {
		resolve(msg);
	} 
	else {
		reject(error);
	}
    });

promise.then(function(msg) {
		//Do something with msg
		console.log(msg)
	}, function(error) {
 		// Failure, do something here
 		console.log(error);
	});
在上例中,我们创建了一个 Promise 对象,并且传入了一个函数对象,注意这个函数并不是异步操作结束后将被调用的函数,而是用来初始化 Promise 的。这个函数接受2个参数 resolve 和 reject (后者可选),并且我们将异步操作全部移植入到这个函数中,当异步操作执行成功之后,调用了 resolve(msg),这是什么意思?很简单,如果我们用第一种方法,resolve 这一行肯定是 callback(msg),也就是调用回调函数并将异步得到的 msg 传给其。所以这里  resolve(msg) 的意思就是通知注册号的回调函数异步操作已经完成了,并且产出了可用的 msg 参数。那么回调函数是在哪里注册的呢?可用看到,之后我们又调用了 promise 的 then 方法,它接收一个函数对象,不错这个函数对象就是我们的回调函数。但是我们的 then 居然接收了2个回调函数, 很显然,第二个函数是用来处理 reject 通知过来的 error 的。
到现在,你可能还看不出 Promise 的优点到底在哪里,别着急,让我们来将例2用 Promise 完成再下定论:

var promise = new Promise(function(resolve) {
		// Some async_work1 start
		// ......get value1
		// Some async_work1 end
		resolve(value1);
    });

promise.then(function(value1) { 
		return new Promise(function(resolve) {
			// Some async_work2 start
			// ......get value2
			// Some async_work2 end
			resolve(value2);
		})
	.then(function(value2) {
		//Do something with value2
		console.log(value2);
	})
为了演示方便这里去掉了对错误的处理。
首先我们 new 了一个 Promise 对象来完成异步操作1,并调用 resolve(value1) 通知下面用 then 方法注册好的回调函数异步操作已经完成,并产出了可用的 value1,然后在这个回调函数中,我们 return 了一个新的 Promise 对象,并且让其来完成异步操作2,并且在异步操作2结束之后 resolve(value2),然后再次调用了 then 方法并注册了与异步操作2对应的回调函数。可以看到,我们可以在回调函数中 return 一个 Promise 对象以此来完成其余的异步操作,由于 return 了一个 Promise,所以可以再次调用 then 方法来注册对应的回调函数。
多么轻松,我们将层层嵌套,令人头疼的代码变成了链式结构。
当然,Promise 不仅仅只有这些功能,为了分清主次,这里不再对其进行深追,可以参考:
http://www.jianshu.com/p/063f7e490e9a

既然有了 Promise,那么何必再加入 RXJS 这个玩意呢?
Promise 有一个缺点,那便是一旦调用了 resolve 或者 reject 之后便返回了,不能再次 resolve 或者 reject,想象一下,若是从端口源源不断地发来消息,每次收到消息就要通知回调函数来处理,那该怎么办呢?
于是,伟大的 RXJS 又出现了!!不得不说,需求推动生产!!

RXJS:

我们已经知道了 Promise 的作用和用法,通过 Promise 对象,我们可以在完成异步工作之后调用 resolve(X) 通知回调函数异步操作已经完成了,并且生产了可使用的 X 对象。既然 RXJS 比 Promise 更厉害,那么它当然也可以完成这个任务,并且可以做得更好。

先从官网搬来rxjs的几个实例概念
  • Observable: 可观察的数据序列.
  • Observer: 观察者实例,用来决定何时观察指定数据.
  • Subscription: 观察数据序列返回订阅实例.
  • OperatorsObservable的操作方法,包括转换数据序列,过滤等,所有的Operators方法接受的参数是上一次发送的数据变更的值,而方法返回值我们称之为发射新数据变更.
  • Subject: 被观察对象.
  • Schedulers: 控制调度并发,即当Observable接受Subject的变更响应时,可以通过scheduler设置响应方式,目前内置的响应可以调用Object.keys(Rx.Subject)查看。

我们最常用也最关心的Observable,四个生命周期:创建 、订阅 、 执行 、销毁。
  • 创建Obervable,返回被观察的序列源实例,该实例不具备发送数据的能力,相比之下通过new Rx.Subject创建的观察对象实例具备发送数据源的能力。
  • 通过序列源实例可以订阅序列发射新数据变更时的响应方法(回调方法)
  • 响应的动作实际上就是Observable的执行
  • 通过序列源实例可以销毁,而当订阅方法发生错误时也会自动销毁。
  • 序列源实例catch方法可以捕获订阅方法发生的错误,同时序列源实例可以接受从catch方法返回值,作为新的序列源实例

可观察的数据序列?发射数据?序列源?都是什么鸟?
 看不懂?没关系,下面还是举例为大家解释。
const search$ = Rx.Observable.fromEvent(input, 'input');  //有个id为input的input DOM

search$.subscribe(function(event) {
	//Do something with event
})

search$.subscribe(function(event) {
	//Do other thing with event
})
上例中我们通过调用 const search$ = Observable.fromEvent(input, 'input') 方法创建了一个 Observable,这个对象有什么用呢?和 Promise 类似,我们可以调用其 subscribe 方法来传递回调函数,也就是上面所提到的观察者 Observer,并且可以有不限制个数的观察者(回调函数)。那么什么时候会触发这些回调函数呢?对于  fromEvent 方法创建的 Observable,在这个例子中,每当  fromEvent  参数中的 input DOM 的 'input' 被触发时,Observable 就会开始执行,执行什么?这里先这么理解,类似 Promise 的 resolve(args) 方法,它会调用 next(args) 方法,通知所有订阅过它的观察者(也就是通过 subscribe 传入的回调函数)有新的产品(args,也就是之前概念里的数据)可以用了,这个时候回调函数都会被调用。
我们可以理解为一个 生产者--消费者 模式,被观察者(Subject)是生产者,可以通过调用 next(args) 生产新的数据 args,所以被观察者同时也是一个数据源(数据args来源),相比 Promise,它可以不限次数的调用 next 方法来生产新的数据,而 Promise 则只可以调用一次 resolve,因此被观察者也就被看成是一个流,一个数据流,类似串口通信一样可以源源不断传来数据供观察者使用。
上面在讲解 Observable 时,我有说过这里先这么理解,难道不应这么理解吗?
其实 Observable 并不能主动调用 next() 方法来发射(生产)数据,但是 Subject 却可以,这便是他们主要的区别,并且 Subject 可以转化成 Observable。那么什么时候应该用 Subject 呢?当我们想要自己生产一些数据时,就可以使用 Subject 了。如在 websocket 通信中,每当 onmessage(msg) 被触发时,我们便可以调用一个全局的 Subject 的 next(msg),然后在其他模块中只要订阅了这个 Subject 就可以使用这些数据(msg)。

RXJS绝对不只这么一点功能,真正强大的还有它的 Operators

让我们再来看一个例子:

//TypeScript

let func =  value => {
	return "message is" + value;
}

const search$ = Observable.fromEvent(input, 'input')  
  .map(e => e.target.value)
  .filter(value => value.length >= 1)
  .throttleTime(100)
  .distinctUntilChanged()
  .switchMap(value => func(value))
  .subscribe(
    x => renderSearchResult(x),
    err => console.error(err)
  )

上面我们到底干了什么?我们一个一个解释。
我们通过 fromEvent()方法创建了一个 Observable
假设输入了 5 次,每次输入的值一次为: a , ab , c , d , c ,并且第 3 次输入的 c 和第 4 次的 d 的时间间隔少于 100ms ,通过画生产线(数据流)的图可以很直观的理解:

---i--i---i-i-----i---|--> (i==input)
map

---a--a---c-d-----c---|-->
b
filter

---a--a---c-d-----c---|-->
b
throttleTime

---a--a---c-------c---|-->
b
distinctUntilChanged

---a--a---c----------|-->
b
switchMap

---x--y---z----------|-->

每个 Operator 都会返回一个新的 Observable 对象。其中 map 为映射(转化)操作,将 event(input event) 型的数据映射到了 event.target.value,然后又用 filter 过滤保留了 value.length >= 1 的 value,throttleTime 方法过滤掉了2次发射时间小于100ms的数据,又用 distinctUntilChanged 过滤了相同的,最后通过 switchMap 再次进行了映射。

怎么样,是不是很强大?

更多的API可以到官网查询。

你可能感兴趣的:(web前端)