可观察对象与其它技术的比较

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.

// declare a publishing operation new Observable((observer) => { subscriber_fn }); // initiate execution observable.subscribe(() => { // observer handles notifications });
      
      // declare a publishing operation
new Observable((observer) => { subscriber_fn });
// initiate execution
observable.subscribe(() => {
      // 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.

// initiate execution new Promise((resolve, reject) => { executer_fn }); // handle return value promise.then((value) => { // handle result here });
      
      // initiate execution
new Promise((resolve, reject) => { executer_fn });
// handle return value
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.

observable.map((v) => 2*v);
      
      observable.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).

promise.then((v) => 2*v);
      
      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.

const sub = obs.subscribe(...); sub.unsubscribe();
      
      const sub = obs.subscribe(...);
sub.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.

obs.subscribe(() => { throw Error('my error'); });
      
      obs.subscribe(() => {
  throw Error('my error');
});
    
  • 承诺会把错误推给其子承诺。

    Promises push errors to the child promises.

promise.then(() => { throw Error('my error'); });
      
      promise.then(() => {
      throw 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.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
let clicks$ = fromEvent(buttonEl, ‘click’);
// Begin listening
let 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()
obs.concat(obsB)
➞1➞2➞3➞5➞7➞'a'➞'b'➞'c'
arr.concat(arrB)
[1,2,3,5,7,'a','b','c']
filter()
obs.filter((v) => v>3)
➞5➞7
arr.filter((v) => v>3)
[5, 7]
find()
obs.find((v) => v>3)
➞5
arr.find((v) => v>3)
5
findIndex()
obs.findIndex((v) => v>3)
➞3
arr.findIndex((v) => v>3)
3
forEach()
obs.forEach((v) => {
  console.log(v);
})
1
2
3
5
7
arr.forEach((v) => {
  console.log(v);
})
1
2
3
5
7
map()
obs.map((v) => -v)
➞-1➞-2➞-3➞-5➞-7
arr.map((v) => -v)
[-1, -2, -3, -5, -7]
reduce()
obs.scan((s,v)=> s+v, 0)
➞1➞3➞6➞11➞18
arr.reduce((s,v) => s+v, 0)
18