【问题标题】:How do I raise an exception in Observable NestJS?如何在 Observable NestJS 中引发异常?
【发布时间】:2021-03-07 08:31:46
【问题描述】:

开始使用 NestJS 遇到问题,如何在 Observable 中抛出异常?

public getGuild(guildId: string, token: string): Observable<Guild | undefined> {
  const guild = this.httpService
    .get<Guild[]>(url, {
      headers: { authorization: token },
    })
    .pipe(map((result) => result.data.find((guild) => guild.id === guildId)));

  return guild;
}

来自外部 API 的响应可能是未定义的,我如何正确检查它,如果未定义,NestJS 会以 404 代码响应?

如果您只是在控制器中返回此响应,NestJS 会返回代码 200,即使响应未定义。

我之前从未遇到过 RxJS,所以我无法解决这个问题。 HttpModule 文档也没有说明这一点。

【问题讨论】:

  • 你试过console.log(guild)吗?您应该会看到类似 Observable { _isScalar: false } 的内容。它永远不会是未定义的,因为 guild 是对您的 observable 的引用,而不是对 observable 的返回值的引用。
  • @MrkSef 是的,我在文中犯了一个错误,它不会是未定义的,它将是 Observable。我需要在 Observable 中处理 undefined 并返回 HttpException。
  • @MrkSef 我更正了问题中的代码。

标签: rxjs nestjs


【解决方案1】:

您可以在流中的任何位置抛出错误。如果results.data 不包含传入guildId 的Guild,则以下sn-p 将引发错误。现在您的结果将被定义(非空/未定义)或错误;

public getGuild(guildId: string, token: string): Observable<Guild> {
  return this.httpService.get<Guild[]>(url, {
    headers: { authorization: token },
  }).pipe(
    map(result => 
      result.data.find(guild => guild.id === guildId)
    ),
    tap(guild => {
      if(guild == null) throw new Error(`No guild with ${guildId} found`);
    })
  );
}

惯用的 RxJS

tap 接受一个流并原样返回相同的流。它可以被视为与 map(x =&gt; x) 相同 - 有些人认为惯用的 RxJS 应该排除 tap 在程序中拥有任何控制流。

这通常适用于用户想要设置全局变量,然后稍后根据该值做出选择的情况。

from(something).pipe(
  tap(x => {
    if(x < 10) this.globalValue = 0;
    if(x > 9) this.globalValue = 25;
    else this.globalValue = 75;
  }),
  mergeMap(x => this.service.save(x, this.globalValue))
);

在这种情况下,您的流不再是“纯”的,因为您无法具体推断this.globalValue 将是什么(也许其他一些代码块在您阅读它之前再次更改了这个全局值)。这使得事情很难测试。你失去了 RxJS 函数式风格的许多好处。

如果您将tap(x =&gt; doSomething) 替换为map(x =&gt; {doSomething; return x}),所有这些问题都是相同的——它们是同一组问题。然而,习惯上,程序员可能会花更多时间了解map 正在做什么,因为他们可能认为tap 没有控制流。


主观部分

在我看来,tap 是一个抛出错误的地方。它应该告诉任何阅读/更新代码的开发人员“此错误是由流中此点的值的形状产生的”。验证和日志记录之类的东西非常适合(惯用的)RxJS 的tap 运算符。在验证期间抛出错误是很自然的。

您将如何编写自定义断言运算符(您可以将它们全部剥离以用于生产构建,而不必担心会破坏任何东西)

function assert<T>(fn: (x: T) => boolean): MonoTypeOperatorFunction<T>{
  return s => s.pipe(
    tap(val => {
      if(!fn(val)) throw new Error("Assertion Failed");
    })
  );
}

使用tap 的实现在清晰性方面非常突出。由于tap这个词没有出现在您的信息流中,因此您在使用过程中也不会遇到任何惯用问题。

public getGuild(guildId: string, token: string): Observable<Guild> {
  return this.httpService.get<Guild[]>(url, {
    headers: { authorization: token },
  }).pipe(
    map(result => 
      result.data.find(guild => guild.id === guildId)
    ),
    assert(result => result != null)
  );
}

这很清楚,我会说。不过,我们可以处理更好的错误消息。


另一种避免争论的方法

如果你不想那样使用tap,这里有另一种用相当惯用的 RxJS 编写它的方法。

public getGuild(guildId: string, token: string): Observable<Guild> {
  return this.httpService.get<Guild[]>(url, {
    headers: { authorization: token },
  }).pipe(
    map(result => {
      const guild = result.data.find(guild => guild.id === guildId);
      if(guild == null) throw new Error(`No guild with ${guildId} found`);
      return guild;
    })
  );
}

现在 - 错误被抛出为“我未能将结果:Guild[] 映射到 guild:Guild”错误。

【讨论】:

  • 一般来说,tap 不应该用于抛出错误。主要它应该只用于自省
  • map(val =&gt; {doSomething; return map}) - 从字面上看只是一种冗长的说法 tap(val =&gt; doSomething) - 我已经用可能是更好的方法更新了我的答案。
【解决方案2】:

如果你想从 observable 中抛出异常,你可以这样做

return this.httpService
  .get(url, options)
  .pipe(
    map(response => response.data),
    map(data => {
      if (data === undefined) {
        throw new NotFoundException();
      }
      return data;
    ),
  );

【讨论】:

  • 就这么简单吗?无论如何,谢谢你,你拯救了我的一天。你能推荐一些有用的学习 RxJS 的资源吗?
  • learnrxjs.io 对我帮助很大
猜你喜欢
  • 2012-10-20
  • 2017-07-27
  • 1970-01-01
  • 1970-01-01
  • 2021-12-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多