介紹
RxJS是一個異步編程的庫,同時它通過observable序列來實現基於事件的編程。它提供了一個核心的類型:Observable,幾個輔助類型(Observer,Schedulers,Subjects),受到Array的擴展操作(map,filter,reduce,every等等)啓發,允許直接處理異步事件的集合。
ReactiveX結合了Observer模式、Iterator模式和函數式編程和集合來構建一個管理事件序列的理想方式。
在RxJS中管理異步事件的基本概念如下:
- Observable:代表了一個調用未來值或事件的集合的概念
- Observer:代表了一個知道如何監聽Observable傳遞過來的值的回調集合
- Subscription:代表了一個可執行的Observable,主要是用於取消執行
- Operators:是一個純函數,允許處理集合與函數式編程風格的操作,比如map、filter、concat、flatMap等
- Subject:相當於一個EventEmitter,它的唯一的方法是廣播一個值或事件給多個Observer
- Schedulers:是一個集中式調度程序來控制併發性,允許我們在setTimeout或者requestAnimationFrame上進行協調計算
第一個例子
正常註冊一個事件監聽函數:
var button = document.querySelector('button');
button.addEventListener('click', () => console.log('Clicked!'));
使用RxJS,你可以創建一個observable來代替:
var button = document.querySelector('button');
Rx.Observable.fromEvent(button, 'click')
.subscrible(() => console.log('Clicked!'));
純粹
使得RxJS變得如此強大的原因是它使用了純函數,這意味着你的代碼很少會發生錯誤。
正常你不會創建一個純函數,代碼的其他部分可能擾亂你的狀態。
var count = 0;
var button = document.querySelector('button');
button.addEventListener('click', () => console.log(`Clicked $(++count) times`));
RxJS將隔離你的狀態
var button = document.querySelector('button');
Rx.Observable.fromEvent(button, 'click')
.scan(count => count + 1, 0)
.subscribe(count => console.log(`Clicked ${count} items`));
scan操作符類似於arrays的reduce操作符。它需要一個回調函數作爲一個參數,函數返回的值將作爲下次調用時的參數。
Flow
RxJS有一系列的操作符來幫你監控事件將如何流動。
這是一個每秒最多點擊一次的程序:
var count = 0;
var rate = 1000;
var lastClick = Date.now() - rate;
var button = document.querySelector('button');
button.addEventListener('click', () => {
if (Date.now() - lastClick >= rate){
console.log(`Clicked ${++count} times`);
lastClick = Date.now();
}
});
使用RxJS:
var button = document.querySelector('button');
Rx.Observable.fromEvent(button, 'click')
.throttleTime(1000)
.scan(count => count + 1, 0)
.subscribe(count => console.log(`Clicked ${count} times`));
另外的控制符還有:filter, delay, debounceTime, take, takeUntil, distinct, distinctUntilChanged等。
值
你可以使用你的observables來轉換值。
這是一個每次點擊添加x座標的程序:
var count = 0;
var rate = 1000;
var lastClick = Date.now() - rate;
var button = document.querySelector('button');
button.addEventListener('click', (event) => {
if (Date.now() - lastClick >= rate){
count += event.clientX;
console.log(count);
lastClick = Date.now();
}
})
使用Rxjs:
var button = document.querySelector('button');
Rx.Observable.fromEvent(button, 'click')
.throttleTime(1000)
.map(event => event.clientX)
.scan((count, clientX) => count + clientX, 0)
.subscribe(count => console.log(count));
另外的producing操作符:pluck、pairwise、sample等
Observable
Observables是一個延遲Push(關於Push的概念見後面)操作數據的集合。它們遵從下表:
Single | Multiple | |
---|---|---|
Pull | Function | Iterator |
Push | Promise | Observable |
舉個例子。下面是一個Observable,當執行subscribed,它將會立即push 1、 2、 3(同步),然後過去一秒後push 4
var observable = Rx.Observable.create(function (observer) {
observer.next(1);
observer.next(2);
observer.next(3);
setTimeout(() => {
observer.next(4);
observer.complete();
}, 1000);
});
爲了調用Observable,然後看這些值,我們需要對這些數據進行訂閱(subscribe)
var observable = Rx.Observable.create(function (observer){
observer.next(1);
observer.next(2);
observer.next(3);
setTimeout(() => {
observer.next(4);
observer.complete();
})
});
console.log('just before subscribe');
observerble.subscribe({
next: x => console.log(`got value` + x),
error: err => console.error('somthing wrong occurred: ' +err),
complete: () => console.log('done')
});
console.log('just after subscribe');
執行結果如下:
just before subscribe
got value 1
got value 2
got value 3
just after sbuscribe
got value 4
done
Pull和Push
Pull和Push是關於數據提供者和數據消費者交互的兩個不同的協議。
什麼是Pull?在Pull系統中,當Consumer收到Producer的數據時,它會自己判斷是否接收該數據,Producer自己並不知道數據將交給哪個Consumer。
所有的JavaScript函數都是一個Pull系統。函數是一個數據提供者,調用函數的代碼是一個consuming(消費者),它將函數返回的值"pulling"出來。
ES2015介紹了generator functions and iterators (function*),它們是另外一種Pull系統。iterator.next() 是Consumer,它從iterator(Producer)中"pulling"出多個值
Producer | Consumer | |
---|---|---|
Pull | 被動:當需要時產生數據 | 主動:決定是否接收數據 |
Push | 主動:自己決定將數據傳給誰 | 被動:響應式接收數據 |
什麼是Push?在Push系統中,Producer決定將數據發往哪個Consumer。Consumer並不知道它自己的值來自哪個Producer
Promise是最常見的一個Push系統。一個Promise(Producer)分發一個結果值給註冊的接口(Consumer),與函數不同的是,Promise當遇到值被"push"給callback時,他會保證它傳遞的對象是正確的。
RxJS介紹了Observables,它是一個新的Push系統。Observable是一個提供多值的Producer,將它們"pushing"給Observers(Consumer)
- Function:計算並同步調用一個值
- generator:計算並同步調用多個值
- Promise:計算後可能(不可能)返回一個值
- Observable:計算然後同步或異步返回一個或多個值
Observable as generalizations of functions
與主流相反,Observable不像EventEmitters,也不像Promise。在某些情況下,Observable的行爲可能像EventEmitters,比如當使用RxJS的Subjects進行多途徑傳播時,但是大部分的情況它們都是不一樣的。
考慮下面的情況:
function foo(){
console.log('Hello');
return 42;
}
var x = foo.call(); // same as foo()
console.log(x);
var y = foo.call(); // same as foo()
console.log(y)
我們期望出現下面的結果:
"Hello"
42
"Hello"
42
當使用Observables時:
var foo = Rx.Observable.create(function (observer){
console.log('Hello');
observer.next(42);
});
foo.subscribe(function (x){
console.log(x);
});
foo.subscribe(function (y){
console.log(y);
})
它們有着同樣地輸出:
"Hello"
42
"Hello"
42
之所以出現這種情況是因爲function和Observables都是延遲(lazy)計算的。如果你不調用function,console.log('Hello')這段代碼是不會執行的。Observables是同樣的,如果你不執行(subscribe)它,代碼也不會執行。“calling”和"subscribing"都是一個獨立的操作:兩個function分別導致兩個結果,兩個Observale subscribes trigger也會分別導致兩個結果。這與EventEmitters截然相反,EventEmitters會共享結果,並且它執行的時候也不會顧忌到底是否有subscribers存在,Observables不會是共享結果,並且也是延遲執行。
Subscribing一個Observable就像調用一個函數一樣
一些人要求Observables是異步的,這是不正確的。看下面這個例子:
console.log('before');
console.log(foo.call());
console.log('after');
你將會看到這樣的輸出:
"before"
"Hello"
42
"after"
使用Observables
console.log('before');
foo.subscribe(function(x) {
console.log(x);
});
console.log('after');
輸出是:
"before"
"Hello"
42
"after"
這證明了foo的訂閱是一個完完全全的異步,就像一個函數一樣。
Observables可以同步或異步地傳遞一個值
Observable和function的不同是什麼呢?隨之時間的流逝,Observables可以“返回”多個值,函數是不可以的。你不可以這麼做:
function foo(){
console.log('Hello');
return 42;
return 100; // 不會執行到這兒
}
函數只能返回一次,Observables可以做到返回多次:
var foo = Rx.Observable.create(function (observer){
console.log('Hello');
observer.next(42);
observer.next(100); // "return another value"
observer.next(200); // "return" yet another
});
console.log('before');
foo.subscribe(function (x){
console.log(x);
});
console.log('after');
同步輸出:
"before"
"Hello"
42
100
200
"after"
你也可以異步返回:
var foo = Rx.Observable.create(function (observer){
console.log('Hello');
observer.next(42);
observer.next(100);
observer.next(200);
setTimeout(() => {
observer.next(300); // 異步
}, 1000);
});
console.log('before');
foo.subscribe(function(x){
console.log(x);
});
console.log('after');
輸出:
"before"
"Hello"
42
100
200
"after"
300
結論:
- func.call()表示“同步給我一個數據”
- observable.subscribe()表示“給我任何數量的值,同步或者異步”
解析一個Observable
Observables使用Rx.Observable.create或者一個構造器創建(create),使用Observer來監聽(subscribed),執行(execute)是通過投遞一個next/error/complete來通知其他的Observer,然後按照各自的意願(disposed)來執行。在一個Observable實例中,這四個方面都是通過編碼實現的,但是這些可能與其他的類型相關,比如Obsrever和Subscription。
Observable的核心點:
- Creating Observables
- Subscribing to Observables
- Executing the Observable
- Disposing Observables
創建一個Observables
Rx.Observable.create是Observable構造器的一個別名,他需要一個參數:一個subscribe函數
下面的例子創建一個Observable,它的作用是每秒鐘輸出字符串'hi':
var observable = Rx.Observable.create(function subscrite(observer){
var id = setInterval(() => {
observer.next('hi')
}, 1000);
});
Observables可以使用create創建,但是我們經常使用creation operators,諸如from,interval等。
在上面的例子中,subscribe函數是描述Observable最重要的一部分,讓我們來看看subscribing是什麼意思。
subscribing to Observables
Observable的observable可以被訂閱(subscribed),就像這樣:
observable.subscribe(x => console.log(x));
observable.scribe和Observable.create(function subscribe(observer) {...})中的subscribe有着相同的名字並不是巧合。在庫中,它們是不同的,但是在實際的用途中你可以在邏輯上把他們想成相同的。
同樣的Observable被多個Observers監聽時,它們是不共享的。
Subscribing一個Observable像調用一個函數一樣,當一個數據被傳遞時提供一個回調
這個addEventListener/removeEventListener這樣的API完全不一樣。observable.subscribe作爲一個給定的觀察者,在Observable中並沒有像listener一樣被註冊。Observable甚至不需要維護一系列的Observers。
Executing observables
代碼Observable.create(function subscribe(observer) {...})代表了一個"Observable execution",它將僅僅在每個Observer的subscribes的延遲計算中。隨着時間的推移,將產生多個結果,同步或者異步。
Observable可以傳遞的有三種類型:
- "Next" notification:傳遞一個數值,諸如Number、String、Object等
- “Error” notification:傳遞一個js異常
- "Complete" notification:什麼值都不傳遞
Next notifications是最重要的也是最常見的類型:它們表示一個實際數據被送到Observer。在Observable Execute執行期間Error和Complete最多會發生一次。
下面的語法是在Observable Grammar or Contract中最好的表達:
next*(error|complete)?
在一個Observable Execute中,0或多個Next notifications可能被傳遞。如果有error或者Complete被傳遞,剩下的next將不會被傳遞。
下面是Observable execute傳遞3個Next notifications的例子:
var observable = Rx.Observable.create(function subscribe(observer) {
observer.next(1);
observer.next(2);
observer.next(3);
observer.complete();
})
下面的例子中,Next notification 4不會被傳遞:
var observable = Rx.Observable.create(function subscribe(observer){
observer.next(1);
observer.next(2);
observer.next(3);
observer.complete();
observer.next(4); // 不會被執行
})
用tru/catch代碼快包裹起來是個好主意:
var observable = Rx.Observable.create(function subscribe(observer) {
try {
observer.next(1);
observer.next(2);
observer.next(3);
observer.complete();
} catch (err) {
observer.error(err); // delivers an error if it caught one
}
});
處理(Disposing)Observable Executions
Observable Executing的個數可能是無限個,Observer中應該處理有限個next,所以我們需要一個API來停止execution。因爲execution在每個Observer中都是獨立的,一旦Observer完成接收值,它必須有一個方法來停止executing。
當 observable.subscribe 被調用,Observer將被附加到一個新創建的Observable execution中,這次調用將返回一個對象,即Subscription:
var subscription = observable.subscribe(x => console.log(x));
Subscription代表了一個進行中的executing,它有一個最小的API允許你取消execution。可以在這裏閱讀更多有關於 Subscription type here 的東西。使用 subscription.unsubscribe() 你可以取消正在進行的execution:
var observable = Rx.Observable.from([10, 20, 30]);
var subscription = observable.subscribe(x => console.log(x));
// Later:
subscription.unsubscribe();
當我們使用create()創建一個Observable時,我們必須定義execution怎麼處理資源。你可以通過返回一個自定義的 unsubscribe 函數來實現該步驟。
var observable = Rx.Observable.create(function subscribe(observer){
var intervalID = setInterval(() => {
observer.next('hi')
});
return function unsubscribe(){
clearInterval(intervalID);
}
})
然後這樣來調用:
function subscribe(observer) {
var intervalID = setInterval(() => {
observer.next('hi');
}, 1000);
return function unsubscribe() {
clearInterval(intervalID);
};
}
var unsubscribe = subscribe({next: (x) => console.log(x)});
// Later:
unsubscribe(); // dispose the resources
Observer
什麼是Observer?一個Observer是Observable傳遞過來的數據的customer。Observers是一個簡單的一些列的回調,next、error、和 complete用來傳遞數據。下面的例子展現了一個典型的Observer對象:
var observer = {
next: x => console.log('Observable got a next value: ' + x),
error: err => console.log('Observable got and error: ' + err),
complete: () => console.log('Observable got a complete notification')
};
爲了使用Observalbe,提供了一個subscribe:
observable.subscribe(observer)
你也可以提供部分回調:
var observer = {
next: x => console.log('Observer got a next value: ' + x),
error: err => console.error('Observer got an error: ' + err),
};
當你訂閱(subscribing)一個Observable時,你也許僅僅只提供一個函數作爲參數:
observable.subscribe(x => console.log('Observer got a next value: ' + x));
在observable.subscribe的內部,他將使用第一個回調創建一個Observer對象作爲一個next handler。所有的callback類型都可能被提供:
observable.subscribe(
x => console.log('Observer got a next value: ' + x),
err => console.error('Observer got an error: ' + err),
() => console.log('Observer got a complete notification')
);
Subscription
什麼是Subscription?一個Subscription代表了一個一次性的資源,通常表示的是一個Observable execution。一個Subscription有一個重要的方法,unsubscribe,它不需要參數,僅僅是處理subscription的資源。在之前的RxJS版本中,Subscription被稱作"Disposable"。
var observable = Rx.Observable.interval(1000);
var subscription = observable.subscribe(x => console.log(x));
// Later:
// This cancels the ongoing Observable execution which
// was started by calling subscribe with an Observer.
subscription.unsubscribe();
一個Subscription實質上是一個unsubscribe()函數,用來釋放資源或者取消一個Observable executions。
Subscriptions也可以放在一起,這樣會導致使用一個unsubscribe()將取消多個Observable executions。你可以這麼做:
var observable1 = Rx.Observable.interval(400);
var observable2 = Rx.Observable.interval(300);
var subscription = observable1.subscribe(x => console.log('first: ' + x));
var childSubscription = observable2.subscribe(x => console.log('second: ' + x));
subscription.add(childSubscription);
setTimeout(() => {
// Unsubscribes BOTH subscription and childSubscription
subscription.unsubscribe();
}, 1000);
當執行時,我們將看到如下輸出:
second: 0
first: 0
second: 1
first: 1
second: 2
Subscriptions有一個remove(otherSubscription)方法,用來移除關聯的Subscirption
Subject
什麼是Subject?一個RxJS Subject是一個特殊類型的Observable,它允許值可以多路廣播給多個Observers。普通的Observables是單路廣播(每個subscribed Observer擁有自己獨立的Observable execution),Subjects是多路廣播。
一個Subject像一個Observable,但是可以多路廣播給Observers。Subjects像Eventmitters:它們維持許多註冊過的監聽器。
每個subject是一個Observable。給定一個Subject,你可以通過提供一個Observer來訂閱(subscribe)它,然後開始正常的接收值。從Observer的角度來看,他不能告知Observer的Observable execution到底是來自一個不同的單路傳播的Observable,還是來自Subject。
在Subject的內部,subscribe並沒有調用一個新的execute去傳遞數據。它只是簡單的註冊Observers列表中的一個Observer,類似於addListener的使用。
每個subject是一個Observer。他是擁有next(v),error(e)和complete()方法的對象。爲了給Subject一個新值,只需要調用 next(theValue),他講多路傳播給註冊過的Observer。
在下面的例子中,我們在Subject中註冊了兩個Observers,我們傳遞一些值給Subject:
var subject = new Rx.Subject();
subject.subscribe({
next: (v) => console.log('observerA: ' + v)
});
subject.subscribe({
next: (v) => console.log('observerB: ' + v)
});
subject.next(1);
subject.next(2);
輸出:
observerA: 1
observerB: 1
observerA: 2
observerB: 2
因爲Subject同時也是一個Observer,這意味着你應該提供一個Subject作爲Observable的subscribe的參數,像這樣:
var subject = new Rx.Subject();
subject.subscribe({
next: (v) => console.log('observerA: ' + v)
});
subject.subscribe({
next: (v) => console.log('observerB: ' + v)
});
var observable = Rx.Observable.from([1, 2, 3]);
observable.subscribe(subject); // You can subscribe providing a Subject
執行如下:
observerA: 1
observerB: 1
observerA: 2
observerB: 2
observerA: 3
observerB: 3
在上面的處理中,我們本質上僅僅是通過Subject將一個單路廣播的Observable execution變爲多路廣播的。這個演示展示了Subjects是怎樣將單路廣播變爲多路廣播的。
這裏有幾個特殊的Subject類型:BehaviorSubject、ReplaySubject和AsyncSubject。
Multicasted Observables
一個"multicasted Observable"的實現是通過Subject的多個訂閱(subscribers)來實現的,然而一個"unicast Observable"僅僅只通知一個單一的Observer。
在後臺,multicast是這樣操作的:Observers訂閱(subscribe)一個相關的Subject,Subject訂閱一個Observable源。
var source = Rx.Observable.from([1, 2, 3]);
var subject = new Rx.Subject();
var multicasted = source.multicast(subject);
// These are, under the hood, `subject.subscribe({...})`:
multicasted.subscribe({
next: (v) => console.log('observerAa: ' + v)
});
muticasted.subscribe({
next: (v) => console.log('observerB: ' + v)
});
// This is, under the hood, `source.subscribe(subject)`:
muticasted.connect();
Reference counting
手動的調用connect()來處理Subscription是很麻煩的。通常,我們希望當第一個Observer到達時,能夠自動connect,當最後一個Observer被移除時,自動取消shared execution。
看看下面這些訂閱發生時的列表:
- 第一個Observer訂閱multicasted Observable
- multicasted observable連接
- next value 0被傳遞給第一個Observer
- 第二個Observer訂閱multicasted Observable
- next value 1被傳遞給第一個Observer
- next value 1被傳遞給第二個Observer
- 第一個Observer解除監聽
- next value2被傳遞給第二個Observer
- 第二個Observer解除監聽
- 與multicasted observable連接的Observable解除連接
看下面的代碼:
var source = Rx.Observable.interval(500);
var subject = new Rx.Subject();
var multicasted = source.multicast(subject);
var subscription1, subscription2, subscriptionConnect;
subscription1 = multicasted.subscribe({
next: (v) => console.log('observerA: ' + v)
});
// We should call `connect()` here, because the first
// subscriber to `multicasted` is interested in consuming values
subscriptionConnect = multicasted.connect();
setTimeout(() => {
subscription2 = multicasted.subscribe({
next: v => console.log('observerB: ' + v)
});
}, 600);
setTimeout(() => {
subscrption1.unscribe();
}, 1200);
// We should unsubscribe the shared Observable execution here,
// because `multicasted` would have no more subscribers after this
setTimeout(() => {
subscription2.unsubscribe();
subscriptionConnect.unsubscribe(); // for the shared Observable execution
}, 2000);
如果我們希望避免一遍遍地調用connect(),我們可以使用ConnectableObservable的refCount()方法(reference counting),它返回一個Observable來跟蹤有多少個訂閱者(subscribers)。當訂閱者從0增加到1時,它將自動調用connect(),只有當訂閱者從1變爲0時,它纔會disconnect。
看下面的例子:
var source = Rx.Observable.interval(500);
var subject = new Rx.Subject();
var refCounted = source.multicast(subject).refCount();
var subscrption1, subscription2, subscriptionConnect;
// This calls `connect()`, because
// it is the first subscriber to `refCounted`
console.log('observerA subscribed');
subscription1 = refCounted.subscribe({
next: (v) => console.log('observerA: ' + v);
});
setTimeout(() => {
console.log('observerB subscribed');
subscription2 = refCounted.subscribe({
next: (v) => console.log('observerA: ' + v)
});
}, 600);
setTimeout(() => {
console.log('observerA unsubscribed');
subscription1.unsubscribe();
}, 1200);
// This is when the shared Observable execution will stop, because
// `refCounted` would have no more subscribers after this
setTimeout(() => {
console.log('observerB unsubscribed');
subscription2.unsubscribe();
}, 2000);
執行結果:
observerA subscribed
observerA: 0
observerB subscribed
observerA: 1
observerB: 1
observerA unsubscribed
observerB: 2
observerB unsubscribed
refCount()方法僅存在ConnectableObservable中,它返回一個Observable,而不是另外的ConnectableObservable。
BehaviorSubject
Subjects的一種變形是BehaviorSubject,它有一個"the current value" 的概念。它存儲了consumer最後一次執行時的value,每當一個Observer訂閱時,它都會立即從BehaviorSubject接收一個"current value"。
例子:
var subject = new Rx.BehaviorSubject(0); // 0 is the inital value
subject.subscribe({
next: v => console.log('observerA: ' + v)
});
subject.next(1);
subject.next(2);
subject.subscribe({
next: v = console.log('observerB: ' + v)
});
subject.next(3);
輸出:
observerA: 0
observerA: 1
observerA: 2
observerB: 2
observerA: 3
observerB: 3
ReplaySubject
功能和它的名字一樣:
var subject = new Rx.ReplaySubject(3); // buffer 3 values for new subscribers
subject.subscribe({
next: v => console.log('observerA: ' + v)
});
subject.next(1);
subject.next(2);
subject.next(3);
subject.next(4);
subject.subscribe({
next: v => console.log('observerB: ' + v)
});
subject.next(5);
輸出:
observerA: 1
observerA: 2
observerA: 3
observerA: 4
observerB: 2
observerB: 3
observerB: 4
observerA: 5
observerB: 5
你還可以指定一個以毫秒爲單位的窗口事時間,除了buffer size之外,決定記錄的值可以重複(時間內)。
var subject = new Rx.ReplaySubject(100, 500);
subject.subscribe({
next: (v) => console.log('observerA: ' + v)
});
var i = 1;
setInterval(() => subject.next(i++), 200);
setTimeout(() => {
subject.subscribe({
next: v => console.log('observerB: ' + v)
});
}, 1000)
下面的輸出中,第二個Observer在最後500ms內得到的數值爲3、 4、 5:
observerA: 1
observerA: 2
observerA: 3
observerA: 4
observerA: 5
observerB: 3
observerB: 4
observerB: 5
observerA: 6
observerB: 6
...
AsyncSubject
AsyncSubject表示只有最後一個Observable execution的值會被髮送給observers,僅僅發生在執行完成時
var subject = new Rx.AsyncSubject();
subject.subscrbe({
next: v => console.log('onbserverA: ' + v)
});
subject.next(1);
subject.next(2);
subject.next(3);
subject.next(4);
subject.subscribe({
next: (v) => console.log('observerB: ' + v)
});
subject.next(5);
subject.complete();
輸出:
observerA: 5
observerB: 5
AsyncSubject類似於一個last() operator,他等待complete通知來傳遞一個唯一的值。
Opeartors
RxJS最有用的一部分就是operators,即使Observable是最基礎的。Operators最基本的要點是允許複雜的代碼變得簡單化。
什麼是Operators?
Opeartors是Obsrevable的方法,就像map(),filter(),merge()等。當它被調用時,它們並不改變已經存在的Observable,而是返回一個基於第一個Observable上新的Observable。
一個Operator本質上是一個純函數,它接收一個Observable,基於其上返回一個新的Observable。在下面的例子中,我們創建了一個自定義的operator方法:
function multiplayByTen(input){
var output = Rx.Observable.create(function subscribe(observer){
input.subscribe({
next: v => observer.next(10 * v),
error: err => observer.error(err),
complete: () => observer.complete()
});
});
return output;
}
var input = Rx.Observable.from([1, 2, 3 ,4]);
var output = multiplayByTen(input);
output.subscribe(x => console.log(x));
輸出爲:
10
20
30
40
注意訂閱(subscribe)的輸出將導致輸入的Observable可觀測的變化。我們稱這個爲"operator subscription chain"。
Instance opeartors versus static operators
什麼是instance operator?最常見的情況是當你引用一個opeartors時,我們假定實現了一個operator,它是Observable實例的一個方法。例如,如果multiplayByTen operator變成一個官方的operator,它看起來會是這樣:
Rx.Observable.prototype.multiplyByTen = function multiplyByTen(){
var input = this;
return Rx.subscrible.create(function subscribe(observer){
input.subccribe({
next: (v) => observer.next(10 * v),
error: (err) => observer.error(err),
complete: () => observer.complete()
});
});
}
Instance operators是一個實例運算符,我們使用它來推斷可觀測的輸入。
注意,input observable不再是一個函數參數:
var observable = Rx.Observable.from([1, 2, 3, 4]).multiplyByTen();
observable.subscribe(x => console.log(x));
什麼是static operator?除了instance operators之外,static operators是直接附加在Observable類上的方法。一個static operator使用內部的this進行操作,但是並不完全依賴它的參數。
static operators是附着在Observable類上的純函數,通常用於創建Observable
最常見的static operators類型是Create Operators,他不是將一個Observable改變成另外一個Observable,它們簡單的獲得一個non-Observable參數,比如number,然後create一個新的Observable。
一個典型的例子是使用interval函數。它獲取一個數值(不是一個Observable)作爲輸入參數,然後輸出一個Observable:
var observable = Rx.Observable.interval(1000 /* number of milliseconds */)
創建一個creation operaor的另外一個例子是create,就是我們之前一直在使用的例子。 all static creation operators here
然而,static operators也許和普通的creation性質不同。一些Combination Operator也許是靜態的,比如merge、combineLatest、concat等。將這些作爲靜態是有意義的,因爲它們把multiple Observales作爲輸入,不只是一個,比如:
var observable1 = Rx.Observable.interval(1000);
var observable2 = Rx.Observable.interval(400);
var merged = Rx.Observable.merge(observable1, observable2);
Marble diagrams
爲了解釋operators是如何工作的,光是文本解釋是不夠的。許多operators和時間有關,它們可能會延遲執行,例如,throttle等。圖標往往能夠比文字更多表達清楚。Marble Diagrams能夠可視化的表現出operators是如何工作的,包括輸入的Observable(s),operator和它的參數,以及輸出的Observable
在一個marble diagram中,隨着時間的流逝,它會描述值("marbles")在Observable execution上傳遞。
你可以在下面看到marble diagram的解析:
- 時間從左往右流動,代表input Observable的execution
- 這些代表Observable傳遞傳來的值
- 這個豎線表示"complete" notification,它表明Observable已經成功完成了。
- 這個方框表示input Observable的operator(上圖)產生出的output Observable(下圖)。方框內的文字表示轉變的屬性。
- 這個Observable是調用operator產生的
- 這個X代表output Observable發出的錯誤,說明因爲某些原因而異常終止。
在這個網站的站點,我們會廣泛的使用marble diagrams去解釋operators是如何工作的。它們也許在其他的地方也很有用,比如單元測試等。
選擇一個operator
你需要爲你的程序選擇一個適當的operator嗎?先從下面的列表選擇一個:
- 我已經有了一個Observable
- 我想改變每個傳遞的值
- 我想選擇每個傳遞值的屬性
- 你應該使用pluck
- 我想查看每個被傳遞的值,但是不影響它們
- 你應該使用do
- 我想過濾某些值
- 基於一個自定義的邏輯
- 你應該使用filter
- 基於一個自定義的邏輯
更多內容參考官網:Choose an operator
operators的分類
Scheduler
什麼是Scheduler?當一個subscription開始工作或者notifications被傳遞,scheduler就會開始調圖。它包含三個組件。
- 一個Scheduler是一個數據結構(data structure)。它知道如何基於優先級或者其它標準進行存儲,執行隊列任務
- 一個Scheduler是一個執行上下文(execution context)。它表示task在哪個地方,什麼時候執行()
- 一個Scheduler是一個(虛擬(virtual))時鐘。它基於scheduler上的getter方法now(),提出了一個"時間(time)"的概念。任務被安排在一個特殊的調度器中,它會遵守給它的時間。
看下面例子中,我們使用之前已經寫過的例子,同步傳遞數值1、2、 3,然後使用observerOn操作符來指定異步調度:
var observable = Rx.Observable.create(function (observer) {
observer.next(1);
observer.next(2);
observer.next(3);
observer.complete();
})
.observerOn(Rx.Scheduler.async);
console.log('just before subscribe');
observable.subscribe({
next: x => console.log('got value ' + x),
error: err => console.error('something wrong occurred: ' + err),
complete: () => console.log('done')
});
console.log('just after subscribe');
輸出:
just before subscribe
just after subscribe
got value 1
got value 2
got value 3
done
注意got value這個語句實在 just after subscribe只有打印輸出的,這和我們看到的代碼順序不一致。這時因爲 observerOn(Rx.Scheduler.async)在Observable.create和最後一個Observer之間引入了一個代理的Observer。讓我們重新爲一些標識符取名,以便讓他們之間有着明顯的差別:
var observable = Rx.Observable.create(function (proxyObserver) {
proxyObserver.next(1);
proxyObserver.next(2);
proxyObserver.next(3);
proxyObserver.complete();
})
.observeOn(Rx.Scheduler.async);
var finalObserver = {
next: x => console.log('got value ' + x),
error: err => console.error('something wrong occurred: ' + err),
complete: () => console.log('done')
};
console.log('just before subscribe');
observable.subscribe(finalObserver);
console.log('just after subscribe');
proxyObserver在observeOn(Rx.Scheduler.async)中創建,它的next(val)方法大概像下面這樣:
var proxyObserver = {
next: (val) => {
Rx.Scheduler.async.schedule(
(x) => finalObserver.next(x),
0 /* delay */,
val /* will be the x for the function above */
);
},
// ...
}
這有點兒像setTimeout或者setInterval是異步調度操作,即使給定的delay爲0。按照慣例,在JS中,setTimeout(fn, 0)知道運行fn函數的時機最早是下一次循環隊列初。這也說明了爲什麼 got value 1是最後運行的。
可以給Scheduler的schedule傳遞一個延時(delay)參數,它可以讓Scheduler內部的時鐘去延時到指定時間。Scheduler的時鐘和真實的時鐘沒有任何關係。它更類似於延時,而不是運行指定的時間。
Scheduler類型
異步Scheduler只是RxJS提供的一種Scheduler。通過使用Scheduler的靜態方法可以創建下面的類型
Scheduler | Purpose |
---|---|
null | 不使用Scheduler, notifications將會被同步和遞歸地交付給Observer。使用這個來進行常量操作或者尾部遞歸操作 |
Rx.Scheduler.queue | Schedules on a queue in the current event frame (trampoline scheduler). Use this for iteration operations. |
Rx.Scheduler.asap | Schedules on the micro task queue, which uses the fastest transport mechanism available, either Node.js' process.nextTick() or Web Worker MessageChannel or setTimeout or others. Use this for asynchronous conversions. |
Rx.Scheduler.async | Schedules work with setInterval. Use this for time-based operations. |