Angular9 與其他技術比較
可觀察對象與其它技術的比較
你可以經常使用可觀察對象(Observable
)而不是承諾(Promise
)來異步傳遞值。 類似的,可觀察對象也可以取代事件處理器的位置。最后,由于可觀察對象傳遞多個值,所以你可以在任何可能構建和操作數組的地方使用可觀察對象。
在這些情況下,可觀察對象的行為與其替代技術有一些差異,不過也提供了一些顯著的優勢。下面是對這些差異的詳細比較。
可觀察對象 vs. 承諾
可觀察對象經常拿來和承諾進行對比。有一些關鍵的不同點:
-
可觀察對象是聲明式的,在被訂閱之前,它不會開始執行。承諾是在創建時就立即執行的。這讓可觀察對象可用于定義那些應該按需執行的菜譜。
-
可觀察對象能提供多個值。承諾只提供一個。這讓可觀察對象可用于隨著時間的推移獲取多個值。
-
可觀察對象會區分串聯處理和訂閱語句。承諾只有
.then()
語句。這讓可觀察對象可用于創建供系統的其它部分使用而不希望立即執行的復雜菜譜。 - 可觀察對象的
subscribe()
會負責處理錯誤。承諾會把錯誤推送給它的子承諾。這讓可觀察對象可用于進行集中式、可預測的錯誤處理。
創建與訂閱
-
在有消費者訂閱之前,可觀察對象不會執行。
subscribe()
會執行一次定義好的行為,并且可以再次調用它。每次訂閱都是單獨計算的。重新訂閱會導致重新計算這些值。Path:"src/observables.ts (observable)" 。
// declare a publishing operation const observable = new Observable<number>(observer => { // Subscriber fn... }); // initiate execution observable.subscribe(() => { // observer handles notifications });
-
承諾會立即執行,并且只執行一次。當承諾創建時,會立即計算出結果。沒有辦法重新做一次。所有的
then
語句(訂閱)都會共享同一次計算。Path:"src/promises.ts (promise)" 。
// initiate execution const promise = new Promise<number>((resolve, reject) => { // Executer fn... }); promise.then(value => { // handle result here });
串聯
-
可觀察對象會區分各種轉換函數,比如映射和訂閱。只有訂閱才會激活訂閱者函數,以開始計算那些值。
Path:"src/observables.ts (chain)" 。
observable.pipe(map(v => 2 * v));
-
承諾并不區分最后的
.then()
語句(等價于訂閱)和中間的.then()
語句(等價于映射)。Path:"src/promises.ts (chain)" 。
promise.then(v => 2 * v);
可取消
-
可觀察對象的訂閱是可取消的。取消訂閱會移除監聽器,使其不再接受將來的值,并通知訂閱者函數取消正在進行的工作。
Path:"src/observables.ts (unsubcribe)" 。
const subscription = observable.subscribe(() => { // observer handles notifications }); subscription.unsubscribe();
- 承諾是不可取消的。
錯誤處理
-
可觀察對象的錯誤處理工作交給了訂閱者的錯誤處理器,并且該訂閱者會自動取消對這個可觀察對象的訂閱。
Path:"src/observables.ts (error)" 。
observable.subscribe(() => { throw Error('my error'); });
-
承諾會把錯誤推給其子承諾。
Path:"src/promises.ts (error)" 。
promise.then(() => { throw Error('my error'); });
速查表
下列代碼片段揭示了同樣的操作要如何分別使用可觀察對象和承諾進行實現。
操作 | 可觀察對象 | 承諾 |
---|---|---|
創建 | new Observable((observer) => { observer.next(123); }); |
new Promise((resolve, reject) => { resolve(123); }); |
轉換 | obs.pipe(map((value) => value * 2)); |
promise.then((value) => value * 2); |
訂閱 | sub = obs.subscribe((value) => { console.log(value) }); |
promise.then((value) => { console.log(value); }) |
取消訂閱 | sub.unsubscribe(); |
承諾被解析時隱式完成。 |
可觀察對象 vs. 事件 API
可觀察對象和事件 API 中的事件處理器很像。這兩種技術都會定義通知處理器,并使用它們來處理一段時間內傳遞的多個值。訂閱可觀察對象與添加事件處理器是等價的。一個顯著的不同是你可以配置可觀察對象,使其在把事件傳給事件處理器之前先進行轉換。
使用可觀察對象來處理錯誤和異步操作在 HTTP 請求這樣的場景下更加具有一致性。
下列代碼片段揭示了同樣的操作要如何分別使用可觀察對象和事件 API 進行實現。
-
“創建與取消”操作。
- 可觀察對象。
// Setup let clicks$ = fromEvent(buttonEl, ‘click’); // Begin listening let subscription = clicks$ .subscribe(e => console.log(‘Clicked’, e)) // Stop listening subscription.unsubscribe();
- 事件 API。
function handler(e) { console.log(‘Clicked’, e); } // Setup & begin listening button.addEventListener(‘click’, handler); // Stop listening button.removeEventListener(‘click’, handler);
-
配置操作。
- 可觀察對象。
監聽按鍵,提供一個流來表示這些輸入的值。
fromEvent(inputEl, 'keydown').pipe( map(e => e.target.value) );
- 事件 API。
不支持配置。
element.addEventListener(eventName, (event) => { // Cannot change the passed Event into another // value before it gets to the handler });
-
訂閱操作。
- 可觀察對象。
observable.subscribe(() => { // notification handlers here });
- 事件 API。
element.addEventListener(eventName, (event) => { // notification handler here });
可觀察對象 vs. 數組
可觀察對象會隨時間生成值。數組是用一組靜態的值創建的。某種意義上,可觀察對象是異步的,而數組是同步的。 在下面的例子中,?
符號表示異步傳遞值。
-
給出值。
- 可觀察對象。
obs: ?1?2?3?5?7 obsB: ?'a'?'b'?'c'
- 數組。
arr: [1, 2, 3, 5, 7] arrB: ['a', 'b', 'c']
-
concat()
。- 可觀察對象。
concat(obs, obsB) ?1?2?3?5?7?'a'?'b'?'c'
- 數組。
arr.concat(arrB) [1,2,3,5,7,'a','b','c']
filter()
。
- 可觀察對象。
obs.pipe(filter((v) => v>3)) ?5?7
- 數組。
arr.filter((v) => v>3) [5, 7]
find()
。
- 可觀察對象。
obs.pipe(find((v) => v>3)) ?5
- 數組。
arr.find((v) => v>3) 5
findIndex()
。
- 可觀察對象。
obs.pipe(findIndex((v) => v>3)) ?3
- 數組。
arr.findIndex((v) => v>3) 3
forEach()
。
- 可觀察對象。
obs.pipe(tap((v) => { console.log(v); })) 1 2 3 5 7
- 數組。
arr.forEach((v) => { console.log(v); }) 1 2 3 5 7
map()
。
- 可觀察對象。
obs.pipe(map((v) => -v)) ?-1?-2?-3?-5?-7
- 數組。
arr.map((v) => -v) [-1, -2, -3, -5, -7]
reduce()
。
- 可觀察對象。
obs.pipe(reduce((s,v)=> s+v, 0)) ?18
- 數組。
arr.reduce((s,v) => s+v, 0) 18