原文鏈接: https://segmentfault.com/a/1190000009924164
RxJS 是響應式編程 (reactive programming) 強大的工具,今天我們將深入介紹 Observables 和 Observers 的內容,以及介紹如何創建自己的操作符 (operators)。
如果你之前已經使用過 RxJS,並希望瞭解 Observable 及 Operators (操作符) 的內部工作原理,那麼這篇文章非常適合你。
什麼是 Observable
Observable 就是一個擁有以下特性的函數:
- 它接收一個
observer
對象作爲參數,該對象中包含next
、error
和complete
方法 - 它返回一個函數,用於在銷燬 Observable 時,執行清理操作
在我們實現的示例中,我們將定義一個簡單的 unsubscribe
函數來實現取消訂閱的功能。然而在 RxJS 中,返回的是 Subcription
對象,該對象中包含一個 unsubscribe
方法。
一個 Observable 對象設置觀察者 (observer),並將它與生產者關聯起來。該生產者可能是 DOM 元素產生的 click
或 input
事件,也可能是更復雜的事件,如 HTTP。
爲了更好地理解 Observable,我們來自定義 Observable。首先,我們先來看一個訂閱的例子:
const node = document.querySelector('input[type=text]');
const input$ = Rx.Observable.fromEvent(node, 'input');
input$.subscribe({
next: (event) => console.log(`You just typed ${event.target.value}!`),
error: (err) => console.log(`Oops... ${err}`),
complete: () => console.log(`Complete!`)
});
該示例中,Rx.Observable.formEvent()
方法接收一個 input
元素和事件名作爲參數,然後返回一個 $input
Observable 對象。接下來我們使用 subscribe()
方法來定於該 Observable 對象。當觸發 input
事件後,對應的值將會傳遞給 observer 對象。
什麼是 Observer
Observer (觀察者) 非常簡單,在上面的示例中,觀察者是一個普通的對象,該對象會作爲 subscribe()
方法的參數。此外 subscribe(next, error, complete)
也是一個有效的語法,但在本文中我們將討論對象字面量的形式。
當 Observable 對象產生新值的時候,我們可以通過調用 next()
方法來通知對應的觀察者。若出現異常,則會調用觀察者的 error()
方法。當我們訂閱 Observable 對象後,只要有新的值,都會通知對應的觀察者。但在以下兩種情況下,新的值不會再通知對應的觀察者:
- 已調用 observer 對象的
complete()
方法 - 消費者對數據不再感興趣,執行取消訂閱操作
此外在執行最終的 subscribe()
訂閱操作前,我們傳遞的值可以經過一系列的鏈式處理操作。執行對應操作的東西叫操作符,每個操作符執行完後會返回一個新的 Observable 對象,然後繼續我們的處理流程。
什麼是 Operator
正如上面所說的,Observable 對象能夠執行鏈式操作,具體如下所示:
const input$ = Rx.Observable.fromEvent(node, 'input')
.map(event => event.target.value)
.filter(value => value.length >= 2)
.subscribe(value => {
// use the `value`
});
上面代碼的執行流程如下:
- 假設用戶在輸入框中輸入字符
a
- Observable 對象響應對應的
input
事件,然後把值傳遞給 observer map()
操作符返回一個新的 Observable 對象filter()
操作符執行過濾操作,然後又返回一個新的 Observable 對象- 最後我們通過調用
subscribe()
方法,來獲取最終的值
簡而言之,Operator 就是一個函數,它接收一個 Observable 對象,然後返回一個新的 Observable 對象。當我們訂閱新返回的 Observable 對象時,它內部會自動訂閱前一個 Observable 對象。
自定義 Observable
Observable 構造函數
function Observable(subscribe) {
this.subscribe = subscribe;
}
每個 subscribe
回調函數被賦值給 this.subscribe
屬性,該回調函數將會被我們或其它 Observable 對象調用。
Observer 示例
在我們深入介紹前,我們先來看一個簡單的示例。之前我們已經創建完 Observable
函數,現在我們可以調用我們的觀察者 (observer),然後傳遞數值 1,然後訂閱它:
const one$ = new Observable((observer) => {
observer.next(1);
observer.complete();
});
one$.subscribe({
next: (value) => console.log(value) // 1
});
即我們訂閱我們創建的 Observable 實例,然後通過 subscribe()
方法調用通過構造函數設置的回調函數。
Observable.fromEvent
下面就是我們需要的基礎結構,即在 Observable 對象上需要新增一個靜態方法 fromEvent
:
Observable.fromEvent = (element, name) => { };
接下來我們將參考 RxJS 爲我們提供的方法來實現自定義的 fromEvent()
方法:
const node = document.querySelector('input');
const input$ = Observable.fromEvent(node, 'input');
按照上面的使用方式,我們的 fromEvent()
方法需要接收兩個參數,同時需要返回一個新的 Observable 對象,具體如下:
Observable.fromEvent = (element, name) => {
return new Observable((observer) => {
});
};
接下來我們來實現事件監聽功能:
Observable.fromEvent = (element, name) => {
return new Observable((observer) => {
element.addEventListener(name, (event) => {}, false);
});
};
那麼我們的 observer
參數來自哪裏? 其實 observer
對象就是包含 next
、error
和 complete
方法的對象字面量。
需要注意的是,我們的 observer 參數不會被傳遞,直到 subscribe() 方法被調用。這意味着 addEventListener() 方法不會被調用,除非你訂閱該 Observable 對象。
當我們調用 subscribe()
方法,之前設置的 this.subscribe
回調函數會被調用,對應的參數是我們定義的 observer 對象字面量,接下來將使用新的值,作爲 next()
方法的參數,調用該方法。
很好,那接下來我們要做什麼?之前版本我們只是設置了監聽,但沒有調用 observer 對象的 next()
方法,接下來讓我們來修復這個問題:
Observable.fromEvent = (element, name) => {
return new Observable((observer) => {
element.addEventListener(name, (event) => {
observer.next(event);
}, false);
});
};
如你所知,當銷燬 Observables 對象時,需要調用一個函數用來執行清理操作。針對目前的場景,在銷燬時我們需要移除事件監聽:
Observable.fromEvent = (element, name) => {
return new Observable((observer) => {
const callback = (event) => observer.next(event);
element.addEventListener(name, callback, false);
return () => element.removeEventListener(name, callback, false);
});
};
我們沒有調用 complete()
方法,因爲該 Observable 對象處理的 DOM 相關的事件,在時間維度上它們可能是無終止的。
現在讓我們來驗證一下最終實現的功能:
const node = document.querySelector('input');
const p = document.querySelector('p');
function Observable(subscribe) {
this.subscribe = subscribe;
}
Observable.fromEvent = (element, name) => {
return new Observable((observer) => {
const callback = (event) => observer.next(event);
element.addEventListener(name, callback, false);
return () => element.removeEventListener(name, callback, false);
});
};
const input$ = Observable.fromEvent(node, 'input');
const unsubscribe = input$.subscribe({
next: (event) => {
p.innerHTML = event.target.value;
}
});
// automatically unsub after 5ssetTimeout(unsubscribe, 5000);
自定義操作符
創建我們自己的操作符應該會更容易一些,現在我們瞭解 Observable
和 Observable
背後的概念。我們將在 Observable 的原型對象上添加一個方法:
Observable.prototype.map = function (mapFn) { };
該方法的功能與 JavaScript 中的 Array.prototype.map
方法類似:
const input$ = Observable.fromEvent(node, 'input')
.map(event => event.target.value);
所以我們需要應用回調函數並調用它,這用於獲取我們所需要的數據。在我們這樣做之前,我們需要流中的最新值。這裏是巧妙的部分,在 map()
操作符中,我們需要訪問 Observable
實例。因爲 map
方法在原型上,我們可以通過以下方式訪問 Observable 實例:
Observable.prototype.map = function (mapFn) {
const input = this;
};
接下來我們在返回的 Observable 對象中執行 input
對象的訂閱操作:
Observable.prototype.map = function(mapFn) {
const input = this;
return new Observable((observer) => {
return input.subscribe();
});
};
我們返回了 input.subscribe() 方法執行的結果,因爲當我們執行取消訂閱操作時,將會依次調用每個 Observable 對象取消訂閱的方法。
最後我們來完善一下 map
操作符的內部代碼:
Observable.prototype.map = function (mapFn) {
const input = this;
return new Observable((observer) => {
return input.subscribe({
next: (value) => observer.next(mapFn(value)),
error: (err) => observer.error(err),
complete: () => observer.complete()
});
});
};
現在我們已經可以執行鏈式操作了:
const input$ = Observable.fromEvent(node, 'input')
.map(event => event.target.value);
input$.subscribe({
next: (value) => {
p.innerHTML = value;
}
});
我有話說
Observable 與 Promise 有什麼區別?
Observable(可觀察對象)是基於推送(Push)運行時執行(lazy)的多值集合。
- Promise
- 返回單個值
- 不可取消的
- Observable
- 隨着時間的推移發出多個值
- 可以取消的
- 支持 map、filter、reduce 等操作符
- 延遲執行,當訂閱的時候纔會開始執行
什麼是 SafeObserver ?
上面的示例中,我們使用一個包含了 next、error、complete 方法的普通 JavaScript 對象來定義觀察者。一個普通的 JavaScript 對象只是一個開始,在 RxJS 5 裏面,爲開發者提供了一些保障機制,來保證一個更安全的觀察者。以下是一些比較重要的原則:
- 傳入的
Observer
對象可以不實現所有規定的方法 (next、error、complete 方法) - 在
complete
或者error
觸發之後再調用next
方法是沒用的 - 調用
unsubscribe
方法後,任何方法都不能再被調用了 complete
和error
觸發後,unsubscribe
也會自動調用- 當
next
、complete
和error
出現異常時,unsubscribe
也會自動調用以保證資源不會浪費 next
、complete
和error
是可選的。按需處理即可,不必全部處理
爲了完成上述目標,我們得把傳入的匿名 Observer
對象封裝在一個 SafeObserver
裏以提供上述保障。
若想進一步瞭解詳細信息,請參考 Observable詳解 文章中 "自定義 Observable" 章節的內容。