发表于: 2019-07-05 23:41:26
1 814
今天完成的事情:(一定要写非常细致的内容,比如说学会了盒子模型,了解了Margin)
明天计划的事情:(一定要写非常细致的内容)
遇到的问题:(遇到什么困难,怎么解决的)
收获:(通过今天的学习,学到了什么知识)
RxJS 库
响应式编程是一种面向数据流和变更传播的异步编程范式(Wikipedia)。RxJS(响应式扩展的 JavaScript 版)是一个使用可观察对象进行响应式编程的库,它让组合异步代码和基于回调的代码变得更简单 (RxJS Docs)。
RxJS 提供了一种对 Observable
类型的实现,直到 Observable
成为了 JavaScript 语言的一部分并且浏览器支持它之前,它都是必要的。这个库还提供了一些工具函数,用于创建和使用可观察对象。这些工具函数可用于:
把现有的异步代码转换成可观察对象
迭代流中的各个值
把这些值映射成其它类型
对流进行过滤
组合多个流
创建可观察对象的函数
RxJS 提供了一些用来创建可观察对象的函数。这些函数可以简化根据某些东西创建可观察对象的过程,比如事件、定时器、承诺等等。比如:
import { from } from 'rxjs';// Create an Observable out of a promiseconst data = from(fetch('/api/endpoint'));// Subscribe to begin listening for async resultdata.subscribe({ next(response) { console.log(response); }, error(err) { console.error('Error: ' + err); }, complete() { console.log('Completed'); }});
Create an observable from a counter <aio-code
import { interval } from 'rxjs';// Create an Observable that will publish a value on an intervalconst secondsCounter= interval(1000);// Subscribe to begin publishing valuessecondsCounter.subscribe(n => console.log(`It's been ${n} seconds since subscribing!`));
Create an observable from an event <aio-code
- import { fromEvent } from 'rxjs';
- const el = document.getElementById('my-element');
- // Create an Observable that will publish mouse movements
- const mouseMoves = fromEvent(el, 'mousemove');
- // Subscribe to start listening for mouse-move events
- const subscription = mouseMoves.subscribe((evt: MouseEvent) => {
- // Log coords of mouse movements
- console.log(`Coords: ${evt.clientX} X ${evt.clientY}`);
- // When the mouse is over the upper-left of the screen,
- // unsubscribe to stop listening for mouse movements
- if (evt.clientX < 40 && evt.clientY < 40) {
- subscription.unsubscribe();
- }
- });
Create an observable that creates an AJAX request <aio-code
import { ajax } from 'rxjs/ajax';// Create an Observable that will create an AJAX requestconst apiData = ajax('/api/data');// Subscribe to create the requestapiData.subscribe(res => console.log(res.status, res.response));
操作符
操作符是基于可观察对象构建的一些对集合进行复杂操作的函数。RxJS 定义了一些操作符,比如
map()
、filter()
、concat()
和flatMap()
。操作符接受一些配置项,然后返回一个以来源可观察对象为参数的函数。当执行这个返回的函数时,这个操作符会观察来源可观察对象中发出的值,转换它们,并返回由转换后的值组成的新的可观察对象。下面是一个简单的例子:
Map operator <aio-code
- import { map } from 'rxjs/operators';
- const nums = of(1, 2, 3);
- const squareValues = map((val: number) => val * val);
- const squaredNums = squareValues(nums);
- squaredNums.subscribe(x => console.log(x));
- // Logs
- // 1
- // 4
- // 9
你可以使用管道来把这些操作符链接起来。管道让你可以把多个由操作符返回的函数组合成一个。
pipe()
函数以你要组合的这些函数作为参数,并且返回一个新的函数,当执行这个新函数时,就会顺序执行那些被组合进去的函数。应用于某个可观察对象上的一组操作符就像一个菜谱 —— 也就是说,对你感兴趣的这些值进行处理的一组操作步骤。这个菜谱本身不会做任何事。你需要调用
subscribe()
来通过这个菜谱生成一个结果。例子如下:
Standalone pipe function <aio-code
- import { filter, map } from 'rxjs/operators';
- const nums = of(1, 2, 3, 4, 5);
- // Create a function that accepts an Observable.
- const squareOddVals = pipe(
- filter((n: number) => n % 2 !== 0),
- map(n => n * n)
- );
- // Create an Observable that will run the filter and map functions
- const squareOdd = squareOddVals(nums);
- // Suscribe to run the combined functions
- squareOdd.subscribe(x => console.log(x));
pipe()
函数也同时是 RxJS 的Observable
上的一个方法,所以你可以用下列简写形式来达到同样的效果:
Observable.pipe function <aio-codeimport { filter, map } from 'rxjs/operators';const squareOdd = of(1, 2, 3, 4, 5) .pipe( filter(n => n % 2 !== 0), map(n => n * n) );// Subscribe to get valuessquareOdd.subscribe(x => console.log(x));
常用操作符
RxJS 提供了很多操作符,不过只有少数是常用的。 下面是一个常用操作符的列表和用法范例,参见 RxJS API 文档。
注意,对于 Angular 应用来说,我们提倡使用管道来组合操作符,而不是使用链式写法。链式写法仍然在很多 RxJS 中使用着。
类别 | 操作 |
---|---|
创建 |
|
组合 | combineLatest , concat , merge , startWith , withLatestFrom , zip |
过滤 | debounceTime , distinctUntilChanged , filter , take , takeUntil |
转换 | bufferTime , concatMap , map , mergeMap , scan , switchMap |
工具 | tap |
多播 | share |
错误处理
除了可以在订阅时提供 error()
处理器外,RxJS 还提供了 catchError
操作符,它允许你在管道中处理已知错误。
假设你有一个可观察对象,它发起 API 请求,然后对服务器返回的响应进行映射。如果服务器返回了错误或值不存在,就会生成一个错误。如果你捕获这个错误并提供了一个默认值,流就会继续处理这些值,而不会报错。
下面是使用 catchError
操作符实现这种效果的例子:
- import { ajax } from 'rxjs/ajax';
- import { map, catchError } from 'rxjs/operators';
- // Return "response" from the API. If an error happens,
- // return an empty array.
- const apiData = ajax('/api/data').pipe(
- map(res => {
- if (!res.response) {
- throw new Error('Value expected!');
- }
- return res.response;
- }),
- catchError(err => of([]))
- );
- apiData.subscribe({
- next(x) { console.log('data: ', x); },
- error(err) { console.log('errors already caught... will not run'); }
- });
重试失败的可观察对象
catchError
提供了一种简单的方式进行恢复,而retry
操作符让你可以尝试失败的请求。可以在
catchError
之前使用retry
操作符。它会订阅到原始的来源可观察对象,它可以重新运行导致结果出错的动作序列。如果其中包含 HTTP 请求,它就会重新发起那个 HTTP 请求。下列代码把前面的例子改成了在捕获错误之前重发请求:
retry operator <aio-code
- import { ajax } from 'rxjs/ajax';
- import { map, retry, catchError } from 'rxjs/operators';
- const apiData = ajax('/api/data').pipe(
- retry(3), // Retry up to 3 times before failing
- map(res => {
- if (!res.response) {
- throw new Error('Value expected!');
- }
- return res.response;
- }),
- catchError(err => of([]))
- );
- apiData.subscribe({
- next(x) { console.log('data: ', x); },
- error(err) { console.log('errors already caught... will not run'); }
- });
不要重试登录认证请求,这些请求只应该由用户操作触发。我们肯定不会希望自动重复发送登录请求导致用户的账号被锁定。
可观察对象的命名约定
由于 Angular 的应用几乎都是用 TypeScript 写的,你通常会希望知道某个变量是否可观察对象。虽然 Angular 框架并没有针对可观察对象的强制性命名约定,不过你经常会看到可观察对象的名字以“$”符号结尾。
这在快速浏览代码并查找可观察对象值时会非常有用。同样的,如果你希望用某个属性来存储来自可观察对象的最近一个值,它的命名惯例是与可观察对象同名,但不带“$”后缀。
比如:
Naming observables <aio-code
- import { Component } from '@angular/core';
- import { Observable } from 'rxjs';
- @Component({
- selector: 'app-stopwatch',
- templateUrl: './stopwatch.component.html'
- })
- export class StopwatchComponent {
- stopwatchValue: number;
- stopwatchValue$: Observable<number>;
- start() {
- this.stopwatchValue$.subscribe(num =>
- this.stopwatchValue = num
- );
- }
- }
评论