如何使用 rxjs 取消嵌套请求

how to cancel nested request with rxjs

buttonClicked$.pipe(
switchMap(value=>makeRequest1),
switchMap(responseOfRequest1=>makeRequest2))
.subscribe()

我需要按顺序发出 2 个 http 请求,并在单击按钮时中止下游挂起的请求。上面的代码是正确的方法吗?我了解新的点击将取消待定 request1。如果点击发生在 request2 期间,request2 是否也会被取消?

为什么第二个 switchMap 可能无法取消

switchMap 运算符仅在从其上游 Observable 接收到下一个事件时(即 makeRequest1)取消其内部 Observable,而不是在源 Observable 发出时(即 buttonClicked$) .因此请考虑以下事件序列:

first button click
first request 1 sent
first response to request 1 received
first request 2 sent
second button click
second request 1 sent
first response to request 2 received
second response to request 1 received

在这种情况下,因为在收到对请求 1 的第二次响应之前收到了对请求 2 的响应,即使第二次按钮单击发生在请求 2 完成之前,请求 2 也从未被取消。

如果请求 1 在请求 2 收到响应之前发出第二个响应,则请求 2 将被取消。但请注意,并不是按钮点击本身取消了它,而是请求 1 的发出取消了它。

解决方案

如果您希望在单击按钮时可靠地取消这两个请求,那么这两个请求需要在同一个 switchMap:

buttonClicked$.pipe(
  switchMap(clickValue => makeRequest1(clickValue).pipe(
    mergeMap(request1Value => makeRequest2(request1Value)))
).subscribe(request2Value => /* ... */);

是的,它确实像间隔一样工作。我没有用网络请求测试它。

每点击一次,第二个可观察间隔就会重新开始。

const { fromEvent, interval, of } = rxjs;
const { switchMap } = rxjs.operators;

const btn = document.getElementById('btn');
const result = document.getElementById('result');
const click$ = fromEvent(btn, 'click');

click$
 .pipe(
   switchMap(value => of('Hello')),
   switchMap(value => interval(1000))
  )
  .subscribe(
   val => result.innerText = val
  )
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.4.0/rxjs.umd.min.js"></script>
<button id="btn">Click Me</button>

<div id="result"></div>

这是正确的行为,因为 switchMap 只有在收到 next 通知时才会取消订阅其内部 Observable。这意味着当您有两个 switchMap 时,第一个必须先发出才能触发第二个中的取消订阅。

您可以通过仅使用一个 switchMap 并将第二个 makeRequest2 调用放入具有 makeRequest1.

的单个链中来避免这种情况
buttonClicked$.pipe(
  switchMap(value => makeRequest1(value).pipe(
    mergeMap(responseOfRequest1 => makeRequest2(responseOfRequest1)),
  )),
  .subscribe()

这样,当 buttonClicked$ 发射时,它会让 switchMap 取消订阅其内部 Observable,内部 Observable 也会取消订阅 mergeMap 如果第二次调用仍在等待中,它将取消第二次调用。