简体   繁体   English

在rxjs中队列http调用

[英]Queue http calls in rxjs

I'm working on a session service that checks if the auth token is expired. 我正在开发一个会话服务,用于检查身份验证令牌是否已过期。 If it is, it makes a call to refresh the token. 如果是,则调用刷新令牌。 During this request, all incoming requests should be queued, and emitted once the request is finished. 在此请求期间,所有传入的请求都应排队,并在请求完成后发出。 After that, all incoming request can pass through without the queue until the token expires again. 之后,所有传入的请求都可以在没有队列的情况下通过,直到令牌再次到期。 I draw a marble diagram for this: 我为此画了一张大理石图:

1. ---a---b---c--d-----e--
2. -t-------f------t------
3. ---a---b---------cd-e--

I named 1. as the incoming$ Observable, 2. is valve$ - if it's true, requests can pass through, if it's false, they should be queued. 我命名为1.作为incoming$ Observable,2。是valve$ - 如果它是真的,请求可以通过,如果它是假的,它们应该排队。 When it turns true, the queued are fired. 当它变为真时,排队被解雇。

What I've done so far? 到目前为止我做了什么? I think this should be done by adding an intermediate Observable called receiver$ which changes its value depending on valve$ . 我认为这应该通过添加一个名为receiver$的中间Observable来完成,它根据valve$更改其值。 When valve$ is true, it just return a simple subject, if it's false, it returns one that's capable of recording values. valve$为真时,它只返回一个简单的主题,如果它是假的,它会返回一个能够记录值的主题。

receiver$ = valve.pipe(
  map((value) => {
    if (value) {
      return new Subject();
    } else {
      return (new Subject()).pipe(
        shareReplay(),
      );
    }
  })
);

And then every new value obtained in incoming$ should be added to the current observable in recevier$ : 然后,在incoming$获得的每个新值应该被添加到recevier$的当前可观察量:

incoming$.pipe(
  combineLatest(receiver$),
).subscribe((incomingValue, recevier) => {
  recevier.next(incomingValue);
});

And here's the part I cannot wrap my head around. 这是我无法绕过的部分。 Whenever valve turns true, I'd need the last two values from receiver$ . 无论何时阀门变为真,我都需要receiver$的最后两个值。 The second last would hold the queue, and the last would hold the active subject. 倒数第二个将保留队列,最后一个将保持活动主题。 By merging them I could achieve my goal. 通过合并它们我可以实现我的目标。 I don't know how to implement this and how subscriptions will be managed. 我不知道如何实现这一点以及如何管理订阅。 Also, this looks overly complicated for such a seemingly simple use case. 而且,对于这种看似简单的用例来说,这看起来过于复杂。

What's the best way of implementing this behavior? 实现此行为的最佳方法是什么?

You can consider a solution along these lines. 你可以考虑这些方面的解决方案。

First you create a Subject through which you emit all the requests you want to make 首先,您创建一个主题,通过该主题发出您想要发出的所有请求

const requests$ = new Subject<Observable<any>>()

Then you create a Subject through which you communicate the state of the valve , ie whether you can execute the request immediately or you have to buffer it 然后创建一个主题,通过它来传达阀门的状态,即您是否可以立即执行请求,或者您必须缓冲它

const valve$ = new Subject<boolean>();

Now you can create a stream which pass the requests only if the valve is open, ie if last value emitted by valve$ is true 现在,您可以创建一个仅在阀门打开时传递请求的流,即如果valve$发出的最后一个值为true

const openStream$ = valve$.pipe(
  switchMap(valve => {
    if (valve) {
      return requests$;
    } else {
      return empty();
    }
  })
);

You can also create a stream which buffers all requests when the valve is closed 您还可以创建一个流,在关闭时缓冲所有请求

const bufferedStream$ = requests$.pipe(
  bufferToggle(valve$.pipe(filter(valve => !valve)), () => valve$.pipe(filter(valve => valve))),
  mergeMap(bufferedCalls => bufferedCalls)
)

Now all you have to do is to merge openStream$ and bufferedStream$ and subscribe to the resulting stream, like this 现在你所要做的就是merge openStream$bufferedStream$subscribe生成的流,就像这样

merge(openStream$, bufferedStream$).pipe(
  mergeMap(request => request)
)
.subscribe(httpCallResult => {// do stuff})

I have tested this solution with the following data, simulating real http calls with Observables of string 我已经使用以下数据测试了此解决方案,使用Observables of string模拟真实的http调用

const requests$ = new Subject<Observable<string>>();
setTimeout(() => {requests$.next(of('A'))}, 50);
setTimeout(() => {requests$.next(of('B'))}, 60);
setTimeout(() => {requests$.next(of('C'))}, 100);
setTimeout(() => {requests$.next(of('D'))}, 110);
setTimeout(() => {requests$.next(of('E'))}, 130);
setTimeout(() => {requests$.next(of('F'))}, 250);
setTimeout(() => {requests$.next(of('G'))}, 260);
setTimeout(() => {requests$.next(of('H'))}, 300);
setTimeout(() => {requests$.next(of('I'))}, 310);
setTimeout(() => {requests$.next(of('L'))}, 330);


const valve$ = new Subject<boolean>();
setTimeout(() => {valve$.next(true)}, 30);
setTimeout(() => {valve$.next(false)}, 80);
setTimeout(() => {valve$.next(true)}, 120);
setTimeout(() => {valve$.next(false)}, 200);
setTimeout(() => {valve$.next(true)}, 290);

You can do this by just using concatMap that merges two different streams based on the value form valve$ . 你可以通过使用concatMap来实现这一点, concatMap根据值valve$合并两个不同的流。 Note that this requires that both valve$ and incoming$ are shared with share() . 请注意,这要求valve$incoming$都与share()共享。

valve$
  .pipe(
    concatMap(v => v
      ? incoming$.pipe(takeUntil(valve$))
      : incoming$
        .pipe(
          takeUntil(valve$),
          bufferCount(Number.POSITIVE_INFINITY),
          mergeAll(),
        )
    ),
  )
  .subscribe(console.log)

Live demo: https://stackblitz.com/edit/rxjs6-demo-d3bsxb?file=index.ts 现场演示: https//stackblitz.com/edit/rxjs6-demo-d3bsxb?file = index.ts

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM