在TypeScript中使用Observables来转换数据

2,882 阅读5分钟

简介

事件处理是构建TypeScript应用程序时常见的实现之一。

注册一个事件以及在事件被触发时执行一些动作需要一个Observable模式,它为事件流提供了一个单向的流,使事件流的调试和错误处理变得更加容易。

在这篇文章中,我们将探讨Observables,如何用它来处理事件驱动的数据,以及错误和异步数据。

    1. 可观察变量简介
    2. 可观察的操作符
    3. 用Observable转换数组
    4. 结合多个运算符进行事件流转换
    5. 用Observables处理错误

可观察变量简介

一般来说,JavaScript中有趣的,但有时令人费脑筋的部分是处理事件。

考虑一下像Facebook这样的实时聊天应用程序,它在一个特定的时间范围内有多个事件发生。一个用户可能正在他的新闻源上输入一些文字,同时收到其他朋友的一些通知和消息,而且没有特定的顺序。现在,处理这些事件是应用程序的责任。这就是Observables的作用。

一个Observable是多个输入值的集合,使用数组方法进行处理,如map,reduce,filter, 等等。它在处理异步操作时非常方便,比如提出HTTP请求、用户输入事件等。

可观察模式是一种设计模式,用于注册一个事件,以及在事件被触发时实现一个流程。专门处理事件的最强大、最流行的JavaScript库之一是Reactive Extensions for JavaScript库,也被称为RxJS。

为了让我们开始工作,我们需要确保我们安装了RxJS库。所以,我们用这个命令安装这个库。

npm install rxjs

已经,这个库有TypeScript需要的所有声明文件,所以不需要独立安装。

为了创建一个Observable,我们需要RxJS的Observable 类型和of 方法,如下所示。

import { of, Observable } from "rxjs"; 
const emitter : Observable<string> = of("Sam", "Ray", "Thomas");

在上面的片段中,我们从RxJS库中导入了of 函数和Observable 类型,然后从字符串"Sam","Ray", 和"Thomas" 中创建了一个Observable

接下来,我们将订阅一个Observable,如下所示。

emitter.subscribe((value: string) => {
  console.log(`Name: ${value}`)
})

在上面的片段中,我们通过调用emitter 变量上的subscribe 方法来注册一个Observer。由于emitter 变量是Observable类型的,我们可以自动访问subscribe 方法。subscribe 方法接收一个函数作为参数,这个函数将为Observable发出的每个值调用一次。

上面的代码将输出以下的Observable流。

Name: Sam
Name: Ray
Name: Thomas

可观察的操作符

Pipeable Operators和Creation Operators是RxJS中的两种操作符。

Pipeable Operators是将一个Observable作为输入并返回另一个Observable的方法。它们可以使用语法observableInstance.pipe(operator()) ,被输送到Observables。它包括 [filter(...)](https://rxjs.dev/api/operators/filter)[mergeMap(...)](https://rxjs.dev/api/operators/mergeMap)方法。

Creation Operators是在调用时创建一个新的Observable的操作符。

创建操作符包括以下内容。

  • [from](https://rxjs.dev/api/index/function/from)
  • [interval](https://rxjs.dev/api/index/function/interval)
  • [of](https://rxjs.dev/api/index/function/of)
  • [range](https://rxjs.dev/api/index/function/range)
  • [throwError](https://rxjs.dev/api/index/function/throwError)

你可以参考RxJS的官方文档以获得完整的操作符列表。

用Observable转换数组

RxJSfrom 方法允许对数组中的数据进行转换。它接收一个数组作为输入,并将数组中的每个数据转换为一个Observable。

让我们来看看下面的代码片断。

const transformArray: Observable<number> = from([1, 2, 3, 4]);
transformArray.subscribe((value: number) => {
  console.log(`value: ${value}`);
});

上面的代码片段使用from 方法将数组中的值转换为Observable,然后在transformArray 变量上调用subscribe 方法。subscribe 方法接受一个函数,该函数对Observable发出的每个值都被执行。

结合多个运算符进行事件流转换

RxJS为我们提供了pipe 方法,该方法允许将多个操作符方法结合起来进行事件流的复杂转换。

让我们考虑一下下面的代码。

const emitter = of(4, 9, 16, 25)
const mapValue = emitter.pipe(
  map((value: number) => {
    return Math.sqrt(value)
  }),
  map((value: number) => {
    return `square root: ${value}`
  })
)
mapValue.subscribe((value: string) => {
  console.log(`string value emitted ${value}`)
})

这里,RxJS的of 方法被用来从数字4,9,16, 和25 创建一个Observable。pipe 方法接收了两个map 方法。第一个map 方法返回输入值的平方根,第二个map 方法将第一个map 方法的输出转换为一个字符串。

最后,subscribe 方法在每个发射的Observable上被调用。

运行上述代码将输出如下。

string value emitted square root: 2 
string value emitted square root: 3 
string value emitted square root: 4
string value emitted square root: 5

观察到的错误

处理Observable流中的异常需要一个结构良好的机制来捕捉这些异常。

让我们考虑一下下面的代码片断。

interface IName {
  value: string;
}
interface IObj {
  name?: IName;
}
const emitObj: Observable<IObj> = of(
  { name: { value: "Bob" } },
  {},
  { name: { value: "Sam" } }
);

在上面的代码片断中,我们创建了IName ,其属性为value ,类型为string 。同时,我们创建了IObj ,并带有一个可选的属性name ,类型为IName 。然后我们创建了emitObj 观察者,它发射了三个值。

现在让我们考虑一下下面这个Observable流。

const returnName = emitObj.pipe(
    map((value: IObj) => {
        return value.name!.value;
    })
);
returnName.subscribe((value: string) => {
    console.log(`name: ${value} `)
});

在上面的片段中,我们创建了一个Observable streamreturnName ,它为输入流值返回name.value 属性。然后,我们订阅这个流并将收到的值记录到控制台。

当我们运行这个片段时,我们将得到以下输出。

name: Bob
TypeError: Cannot read property 'value' of undefined

这个错误的发生是因为在我们的Observable流中发出的第二个值没有一个名字属性,导致了一个未定义的值。

为了解决这个错误,我们可以为我们的Observable流中的subscribe 函数创建一个错误处理器。

让我们考虑下面的情况。

returnName.subscribe(
  // called for each observable value
  (value: string| null) => {
    console.log(`name: ${value} `);
  },
  // called if an error occurs
  (error: unknown) => {
    console.log(`error : ${error}`);
  },
  // called when execution is done
  () => {
    console.log(`done`);
  }
);

在这里,我们为subscribe 方法提供了三个函数作为参数,每个由Observable流发出的值都会调用第一个函数,如果发生错误则调用第二个函数,最后一个函数在subscribe 方法执行完成后调用。

当我们运行这个片段时,我们将得到以下输出。

name: Bob
TypeError: Cannot read property 'value' of undefined

这里发生的情况是,Observable流发出的第一个值由提供给subscribe 方法的第一个函数处理。由Observable流发出的第二个值导致了一个错误,这个错误被提供给subscribe 方法的错误函数捕获。由于Observable流中发生的错误,最后一个函数没有被调用。

catchError

另一种处理Observable错误的方法是在Observable流本身中使用catchError 操作符,这样我们就可以足够早地捕获这些错误。

让我们考虑一下下面的代码片断。

const returnName = emitObj.pipe(
    map((value: IObj) => {
        return value!.name!.value;
    }),
    catchError((error: unknown) => {
        console.log(`stream caught : ${error}`);
        return of(null);
    })
);

在这里,我们在Observable流中加入了一个catchError 操作符。在这个函数中,我们将错误信息记录到控制台,然后返回一个Observable值:null 。通过这个实现,即使Observable中发生错误,最后一个函数也会被触发。

当我们运行这个片段时,我们将得到以下输出。

Name: Bob
stream caught : TypeError: Cannot read property 'value' of undefined
received null
done

这里发生的情况是,map 方法对Observable流的第二个值产生了错误,之后我们的catchError 函数被调用,出现了以下错误:"不能读取未定义的属性'值'。" 同时,最后一个方法被触发。

一旦Observable流内发生错误,该流将停止发射值。这就是为什么emitObj Observable流不发射其最后一个参数的值。

总结

在这篇文章中,我们已经探讨了用Observable流进行数据转换以及如何实现它们。此外,我们还讨论了Observable的错误处理,并提供了在使用Observable流时实现错误处理的策略。

希望你能发现这篇帖子的信息量和帮助。你也可以查看RxJS的官方文档,深入了解RxJS的Observable模块。另外,我推荐Brian Troncone的learnrxjs.io,以获得对RxJS概念更直观的解释。

The postUsing Observables to transform data in TypeScriptappeared first onLogRocket Blog.