前言:
最近在学习 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
- : 观察数据序列返回订阅实例.
Operators
- :
Observable
- 的操作方法,包括转换数据序列,过滤等,所有的
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可以到官网查询。