填寫這份《一分鐘調查》,幫我們(開發組)做得更好!去填寫Home

可觀察物件與其它技術的比較

Observables compared to other techniques

你可以經常使用可觀察物件(Observable)而不是承諾(Promise)來非同步傳遞值。 類似的,可觀察物件也可以取代事件處理器的位置。最後,由於可觀察物件傳遞多個值,所以你可以在任何可能建構和運算元組的地方使用可觀察物件。

You can often use observables instead of promises to deliver values asynchronously. Similarly, observables can take the place of event handlers. Finally, because observables deliver multiple values, you can use them where you might otherwise build and operate on arrays.

在這些情況下,可觀察物件的行為與其替代技術有一些差異,不過也提供了一些顯著的優勢。下面是對這些差異的詳細比較。

Observables behave somewhat differently from the alternative techniques in each of these situations, but offer some significant advantages. Here are detailed comparisons of the differences.

可觀察物件 vs. 承諾

Observables compared to promises

可觀察物件經常拿來和承諾進行對比。有一些關鍵的不同點:

Observables are often compared to promises. Here are some key differences:

  • 可觀察物件是宣告式的,在被訂閱之前,它不會開始執行。承諾是在建立時就立即執行的。這讓可觀察物件可用於定義那些應該按需執行的菜譜。

    Observables are declarative; computation does not start until subscription. Promises execute immediately on creation. This makes observables useful for defining recipes that can be run whenever you need the result.

  • 可觀察物件能提供多個值。承諾只提供一個。這讓可觀察物件可用於隨著時間的推移獲取多個值。

    Observables provide many values. Promises provide one. This makes observables useful for getting multiple values over time.

  • 可觀察物件會區分串聯處理和訂閱語句。承諾只有 .then() 語句。這讓可觀察物件可用於建立供系統的其它部分使用而不希望立即執行的複雜菜譜。

    Observables differentiate between chaining and subscription. Promises only have .then() clauses. This makes observables useful for creating complex transformation recipes to be used by other part of the system, without causing the work to be executed.

  • 可觀察物件的 subscribe() 會負責處理錯誤。承諾會把錯誤推送給它的子承諾。這讓可觀察物件可用於進行集中式、可預測的錯誤處理。

    Observables subscribe() is responsible for handling errors. Promises push errors to the child promises. This makes observables useful for centralized and predictable error handling.

建立與訂閱

Creation and subscription

  • 在有消費者訂閱之前,可觀察物件不會執行。subscribe() 會執行一次定義好的行為,並且可以再次呼叫它。每次訂閱都是單獨計算的。重新訂閱會導致重新計算這些值。

    Observables are not executed until a consumer subscribes. The subscribe() executes the defined behavior once, and it can be called again. Each subscription has its own computation. Resubscription causes recomputation of values.

src/observables.ts (observable)
      
      // declare a publishing operation
const observable = new Observable<number>(observer => {
  // Subscriber fn...
});

// initiate execution
observable.subscribe(value => {
  // observer handles notifications
});
    
  • 承諾會立即執行,並且只執行一次。當承諾建立時,會立即計算出結果。沒有辦法重新做一次。所有的 then 語句(訂閱)都會共享同一次計算。

    Promises execute immediately, and just once. The computation of the result is initiated when the promise is created. There is no way to restart work. All then clauses (subscriptions) share the same computation.

src/promises.ts (promise)
      
      // initiate execution
let promise = new Promise<number>(resolve => {
  // Executer fn...
});
promise.then(value => {
  // handle result here
});
    

串聯

Chaining

  • 可觀察物件會區分各種轉換函式,比如對映和訂閱。只有訂閱才會啟用訂閱者函式,以開始計算那些值。

    Observables differentiate between transformation function such as a map and subscription. Only subscription activates the subscriber function to start computing the values.

src/observables.ts (chain)
      
      observable.pipe(map(v => 2 * v));
    
  • 承諾並不區分最後的 .then() 語句(等價於訂閱)和中間的 .then() 語句(等價於對映)。

    Promises do not differentiate between the last .then clauses (equivalent to subscription) and intermediate .then clauses (equivalent to map).

src/promises.ts (chain)
      
      promise.then(v => 2 * v);
    

可取消

Cancellation

  • 可觀察物件的訂閱是可取消的。取消訂閱會移除監聽器,使其不再接受將來的值,並通知訂閱者函式取消正在進行的工作。

    Observable subscriptions are cancellable. Unsubscribing removes the listener from receiving further values, and notifies the subscriber function to cancel work.

src/observables.ts (unsubcribe)
      
      const subscription = observable.subscribe(() => {
  // observer handles notifications
});

subscription.unsubscribe();
    
  • 承諾是不可取消的。

    Promises are not cancellable.

錯誤處理

Error handling

  • 可觀察物件的錯誤處理工作交給了訂閱者的錯誤處理器,並且該訂閱者會自動取消對這個可觀察物件的訂閱。

    Observable execution errors are delivered to the subscriber's error handler, and the subscriber automatically unsubscribes from the observable.

src/observables.ts (error)
      
      observable.subscribe(() => {
  throw new Error('my error');
});
    
  • 承諾會把錯誤推給其子承諾。

    Promises push errors to the child promises.

src/promises.ts (error)
      
      promise.then(() => {
  throw new Error('my error');
});
    

速查表

Cheat sheet

下列程式碼片段揭示了同樣的操作要如何分別使用可觀察物件和承諾進行實現。

The following code snippets illustrate how the same kind of operation is defined using observables and promises.

操作

Operation

可觀察物件

Observable

承諾

Promise

建立

Creation

new Observable((observer) => {
  observer.next(123);
});
new Promise((resolve, reject) => {
  resolve(123);
});

轉換

Transform

obs.pipe(map((value) => value * 2));
promise.then((value) => value * 2);

訂閱

Subscribe

sub = obs.subscribe((value) => {
  console.log(value)
});
promise.then((value) => {
  console.log(value);
});

取消訂閱

Unsubscribe

sub.unsubscribe();

承諾被解析時隱式完成。

Implied by promise resolution.

可觀察物件 vs. 事件 API

Observables compared to events API

可觀察物件和事件 API 中的事件處理器很像。這兩種技術都會定義通知處理器,並使用它們來處理一段時間內傳遞的多個值。訂閱可觀察物件與新增事件處理器是等價的。一個顯著的不同是你可以配置可觀察物件,使其在把事件傳給事件處理器之前先進行轉換。

Observables are very similar to event handlers that use the events API. Both techniques define notification handlers, and use them to process multiple values delivered over time. Subscribing to an observable is equivalent to adding an event listener. One significant difference is that you can configure an observable to transform an event before passing the event to the handler.

使用可觀察物件來處理錯誤和非同步操作在 HTTP 請求這樣的場景下更加具有一致性。

Using observables to handle events and asynchronous operations can have the advantage of greater consistency in contexts such as HTTP requests.

下列程式碼片段揭示了同樣的操作要如何分別使用可觀察物件和事件 API 進行實現。

Here are some code samples that illustrate how the same kind of operation is defined using observables and the events API.

可觀察物件

Observable

事件 API

Events API

建立與取消

Creation & cancellation

// Setup
const clicks$ = fromEvent(buttonEl, ‘click’);
// Begin listening
const subscription = clicks$
  .subscribe(e => console.log(‘Clicked’, e))
// Stop listening
subscription.unsubscribe();
function handler(e) {
  console.log(‘Clicked’, e);
}
// Setup & begin listening
button.addEventListener(‘click’, handler);
// Stop listening
button.removeEventListener(‘click’, handler);

訂閱

Subscription

observable.subscribe(() => {
  // notification handlers here
});
element.addEventListener(eventName, (event) => {
  // notification handler here
});

配置

Configuration

監聽按鍵,提供一個流來表示這些輸入的值。

Listen for keystrokes, but provide a stream representing the value in the input.

fromEvent(inputEl, 'keydown').pipe(
  map(e => e.target.value)
);

不支援配置。

Does not support configuration.

element.addEventListener(eventName, (event) => {
  // Cannot change the passed Event into another
  // value before it gets to the handler
});

可觀察物件 vs. 陣列

Observables compared to arrays

可觀察物件會隨時間產生值。陣列是用一組靜態的值建立的。某種意義上,可觀察物件是非同步的,而陣列是同步的。 在下面的例子中,➞ 符號表示非同步傳遞值。

An observable produces values over time. An array is created as a static set of values. In a sense, observables are asynchronous where arrays are synchronous. In the following examples, ➞ implies asynchronous value delivery.

可觀察物件

Observable

陣列

Array

給出值

Given

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