【问题标题】:Is subscribing inside RxJS tap, bad practice for an asynchronous logic?在 RxJS tap 中订阅是异步逻辑的坏习惯吗?
【发布时间】:2021-06-14 03:45:33
【问题描述】:

我有两个 http 请求,其中第二个(日志请求)应该在第一个(订单请求)发出一个值后订阅,我做了一些逻辑,不应该被日志请求阻止 忽略日志请求的结果。

据我所知,点击是

用于执行副作用

https://rxjs.dev/api/operators/tap

并且由于我假设 Log 请求及其响应是 Order 请求的副作用,所以在 tap 内订阅是不好的做法吗?有没有更流畅和 Rx​​JS 的方式来处理这个问题?

const order = of('2- created order').pipe(
  delay(100),
  tap(console.log)
);

const log = of('4- logged info to log server').pipe(
  delay(500),
  tap(console.log)
);

console.log('1- started creating order');
order
  .pipe(tap(() => log.subscribe()))
  .subscribe(() => console.log("3- didn't wait for log server"));

StackBlitz

【问题讨论】:

  • 一般是的,因为没有办法取消订阅。但是,在您的情况下,您似乎甚至不想取消订阅,也不关心结果,您甚至可能不希望它阻止 complete 通知(就像 ignoreElements() 运营商那样)所以我猜猜这是一个例外,它很好。

标签: javascript rxjs


【解决方案1】:

是的,这绝对是不好的做法。

你说得对,tap 存在副作用,但那些不应该涉及其他流,它们应该是简单的副作用,例如分配变量或控制台日志记录等。

问题是您通常不想在subscribe 内部订阅pipe,因为这样做会导致非常不可预测且难以维护代码。

例如,tab 中的 subscribe 看起来很无害,但想象一下,如果您正在收听连续的数据流,您会有多少订阅?您是否愿意跟踪它们以取消订阅,这最终会不会很难理解/调试等等...

您的代码的问题在于您在某种程度上以命令式的方式思考(例如“执行此操作,然后执行此操作,然后...”),而不是根据流进行思考。

所以基本上在我看来,而不是像“在那之前我该怎么做?”这样的想法。您应该考虑如何处理流以及可以对其执行的操作顺序。

在您的情况下,您是否有任何理由要在订阅中而不是在 pipe 中打印第三条消息?

为什么不直接做以下事情?

order
  .pipe(
    tap(() => console.log("3- didn't wait for log server")),
    switchMap(() => log)
  )
  .subscribe();

(喜欢这里:https://stackblitz.com/edit/rxjs-aace8i?file=index.ts)


如果可以的话,我想分析一下你的问题...

开始

我有两个 http 请求,其中第二个(日志请求) 应该在第一个(订单请求)发出值后订阅

这似乎是一个简单的例子,有一个初始的 observable (Order) 并且需要使用映射运算符移动到另一个 (Log),我假设你想丢弃第一个并移动到第二个所以我选择switchMap(或者你可以使用concatMap或mergeMap)

然后我们得到:

我做了一些不应该被日志请求阻塞的逻辑 日志请求的结果被忽略。

因为我们已经考虑过如何处理这两个 observables,如果我们阅读您的句子,它确实说明我们只是希望在第一个和第二个 observables 之间发生副作用,并且它无论如何都会忽略流的值,所以它显然需要一个简单的tap


我很抱歉这个相当长的信息我希望它听起来不会太迂腐:P

我基本上想说的是,您应该始终查看您的流,并考虑如何根据 rxjs 编程风格和您的需求将所有内容组合在一起,而不是某些做某事的方式是否可以接受,因为这让我意识到你已经怀疑它是否不是最好的解决方案。

【讨论】:

  • "你有什么理由想在订阅中打印第三条消息而不是在管道中?"是的,因为它是由应用程序中其他地方的另一个服务订阅的,他们需要访问订单请求的结果。第三条消息在这里只是一个占位符。
  • mh...所以基本上你想在一个服务中创建一个可观察的,你可以在另一个服务中订阅,并且订阅也会触发日志作为副作用?我不是 100% 确定我得到了全貌,但如果我理解正确,我建议不要在管道内订阅,而是使用一个主题并调用它,以这种方式创建你想要的副作用并且您将在日志服务中对该主题进行一次订阅。
  • 这有帮助吗?但是无论如何,我认为有一些方法可以解决它,并且您不想在管道中订阅! :P
  • 啊,是的,我刚刚注意到@BizzBob 给出的答案与我的想法相似
【解决方案2】:

正如@martin 在评论中提到的,在管道内订阅通常是不好的(不是专门点击,而是任何运算符),因为无法处理清理订阅。

一般来说,最好使用“Higher-Order Mapping Operators”之一,因为它们会处理您的 observable 的订阅、发射和取消订阅。

有没有更流畅和 Rx​​JS 的方式来处理这个问题

不确定这是否会被视为“光滑”:-),但我认为如果将Subject 用作日志消息的专用流,它可以很好地分离关注点;然后创建一个订阅以执行您的服务器日志记录逻辑:

const logMessage$ = new Subject<string>();
logMessage$
  .pipe(mergeMap(logToServer))
  .subscribe();               

然后在您的其他代码中,您可以调用 logMessage$.next() 来触发服务器日志记录逻辑,而不是订阅,而不会阻碍您的 order 流的流动:

order.pipe(tap(o => logMessage$.next(o)));

这是更新后的StackBlitz。

【讨论】:

  • 抱歉,我不明白您为什么要在那里使用mergeMap,对于logMessage$ 主题来说,一个简单的非管道订阅难道不够吗?
  • 如果logToServer只做了一些同步逻辑,那么mergeMap就不需要了。但是,在这种情况下,logToServer 返回一个需要订阅才能执行的 observable,因此需要使用 mergeMap 来处理订阅。
猜你喜欢
  • 2017-02-22
  • 2021-07-17
  • 1970-01-01
  • 1970-01-01
  • 2023-01-05
  • 1970-01-01
  • 2022-12-20
  • 2022-11-20
  • 1970-01-01
相关资源
最近更新 更多