可观察对象用法实战

Practical observable usage

这里示范了一些在某种领域中可观察对象会特别有用的例子。

Here are some examples of domains in which observables are particularly useful.

输入提示(type-ahead)建议

Type-ahead suggestions

可观察对象可以简化输入提示建议的实现方式。典型的输入提示要完成一系列独立的任务:

Observables can simplify the implementation of type-ahead suggestions. Typically, a type-ahead has to do a series of separate tasks:

  • 从输入中监听数据。

    Listen for data from an input.

  • 移除输入值前后的空白字符,并确认它达到了最小长度。

    Trim the value (remove whitespace) and make sure it’s a minimum length.

  • 防抖(这样才能防止连续按键时每次按键都发起 API 请求,而应该等到按键出现停顿时才发起)

    Debounce (so as not to send off API requests for every keystroke, but instead wait for a break in keystrokes).

  • 如果输入值没有变化,则不要发起请求(比如按某个字符,然后快速按退格)。

    Don’t send a request if the value stays the same (rapidly hit a character, then backspace, for instance).

  • 如果已发出的 AJAX 请求的结果会因为后续的修改而变得无效,那就取消它。

    Cancel ongoing AJAX requests if their results will be invalidated by the updated results.

完全用 JavaScript 的传统写法实现这个功能可能需要大量的工作。使用可观察对象,你可以使用这样一个 RxJS 操作符的简单序列:

Writing this in full JavaScript can be quite involved. With observables, you can use a simple series of RxJS operators:

import { fromEvent } from 'rxjs'; import { ajax } from 'rxjs/ajax'; import { map, filter, debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators'; const searchBox = document.getElementById('search-box'); const typeahead = fromEvent(searchBox, 'input').pipe( map((e: KeyboardEvent) => e.target.value), filter(text => text.length > 2), debounceTime(10), distinctUntilChanged(), switchMap(() => ajax('/api/endpoint')) ); typeahead.subscribe(data => { // Handle the data from the API });
Typeahead
      
      
  1. import { fromEvent } from 'rxjs';
  2. import { ajax } from 'rxjs/ajax';
  3. import { map, filter, debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
  4.  
  5. const searchBox = document.getElementById('search-box');
  6.  
  7. const typeahead = fromEvent(searchBox, 'input').pipe(
  8. map((e: KeyboardEvent) => e.target.value),
  9. filter(text => text.length > 2),
  10. debounceTime(10),
  11. distinctUntilChanged(),
  12. switchMap(() => ajax('/api/endpoint'))
  13. );
  14.  
  15. typeahead.subscribe(data => {
  16. // Handle the data from the API
  17. });

指数化退避

Exponential backoff

指数化退避是一种失败后重试 API 的技巧,它会在每次连续的失败之后让重试时间逐渐变长,超过最大重试次数之后就会彻底放弃。 如果使用承诺和其它跟踪 AJAX 调用的方法会非常复杂,而使用可观察对象,这非常简单:

Exponential backoff is a technique in which you retry an API after failure, making the time in between retries longer after each consecutive failure, with a maximum number of retries after which the request is considered to have failed. This can be quite complex to implement with promises and other methods of tracking AJAX calls. With observables, it is very easy:

import { pipe, range, timer, zip } from 'rxjs'; import { ajax } from 'rxjs/ajax'; import { retryWhen, map, mergeMap } from 'rxjs/operators'; function backoff(maxTries, ms) { return pipe( retryWhen(attempts => range(1, maxTries) .pipe( zip(attempts, (i) => i), map(i => i * i), mergeMap(i => timer(i * ms)) ) ) ); } ajax('/api/endpoint') .pipe(backoff(3, 250)) .subscribe(data => handleData(data)); function handleData(data) { // ... }
Exponential backoff
      
      
  1. import { pipe, range, timer, zip } from 'rxjs';
  2. import { ajax } from 'rxjs/ajax';
  3. import { retryWhen, map, mergeMap } from 'rxjs/operators';
  4.  
  5. function backoff(maxTries, ms) {
  6. return pipe(
  7. retryWhen(attempts => range(1, maxTries)
  8. .pipe(
  9. zip(attempts, (i) => i),
  10. map(i => i * i),
  11. mergeMap(i => timer(i * ms))
  12. )
  13. )
  14. );
  15. }
  16.  
  17. ajax('/api/endpoint')
  18. .pipe(backoff(3, 250))
  19. .subscribe(data => handleData(data));
  20.  
  21. function handleData(data) {
  22. // ...
  23. }