Using RxJS how to buffer function calls until an other async function call has resolved

前提是你 提交于 2019-12-24 08:36:49

问题


How can I use RxJS to buffer function calls until another async function has resolved?

Here is a simple example of what I'd like to accomplish

function asyncFunc(time) {
  setTimeout(() => {
      console.log('asyncFunc has resolved');
   }, time);
}

function funcToBuffer(time) {
  setTimeout(() => {
    console.log(time);
  }, time);
}

asyncFunc(3000);

funcToBuffer(1000);
funcToBuffer(2000);
funcToBuffer(4000);
funcToBuffer(5000);

asyncFunc(8000);

funcToBuffer(6000);
funcToBuffer(7000);

At the moment this code will print:

1000
2000
asyncFunc has resolved
4000
5000
6000
7000
asyncFunc has resolved

What I want is for this to print:

asyncFunc has resolved
1000
2000    
4000
5000
asyncFunc has resolved
6000
7000

In essence, I want some kind of control flow that allows me to call funcToBuffer whenever I feel like, but under the hood, I want it to hold on executing whenever asyncFunc is executing and waiting to be resolved. Once asyncFunc has resolved, funcToBuffer calls should no longer be buffered and be executed right away.

I have tried playing with the buffer operator but I wasn't able to achieve the desired outcome.


回答1:


If I understand it right, your main goal is to control the execution of a sequence of functions through a mechanism that buffers them until something happens, and that something is exactly what triggers the execution of the functions buffered.

If this is correct, the following could be the basis for a possible solution to your problem

const functions$ = new Subject<() => any>();

const buffer$ = new Subject<any>();
const executeBuffer$ = new Subject<any>();
const setBuffer = (executionDelay: number) => {
    buffer$.next();
    setTimeout(() => {
        executeBuffer$.next();
    }, executionDelay);
}

const functionBuffer$ = functions$
.pipe(
    bufferWhen(() => buffer$),
);

zip(functionBuffer$, executeBuffer$)
.pipe(
    tap(functionsAndExecuteSignal => functionsAndExecuteSignal[0].forEach(f => f()))
)
.subscribe();

Let me explain a bit the code.

First thing, we build functions$, i.e. an Observable of the functions we want to control. The Observable is built using a Subject, since we want to be able to control the notification of such Observable programmatically. In other words, rather than kicking the execution of a function like this funcToBuffer(1000), we create the function (as an object) and ask the functions$ Observable to emit the function like this

const aFunction = () => setTimeout(() => {console.log('I am a function that completes in 1 second');}, 1000);
functions$.next(aFunction);

In this way we have created a stream of functions that eventually will be executed.

Second thing, we create 2 more Observables, buffer$ and executeBuffer$, again using Subjects. Such Observables are used to signal when we have to create a buffer out of the functions emitted so far by functions$ and when we have to start the execution of the functions buffered.

These last 2 Observables are used in the function setBuffer. When you call setBuffer you basically say: please, create a buffer with all the functions which have been emitted so far by functions$ and start executing them after the executionDelay time specified as parameter.

The buffering part is performed by the functionBuffer$ Observable which is created using bufferWhen operator. The execution part is implemented leveraging the zip operator, that allows us to set the rhythm of execution of the functions based on the emissions of executeBuffer$ Observable.

You can test the above code setting up the following test data.

let f: () => any;
setBuffer(3000);
f = () => setTimeout(() => {console.log('f1');}, 1000);
functions$.next(f);
f = () => setTimeout(() => {console.log('f2');}, 2000);
functions$.next(f);
f = () => setTimeout(() => {console.log('f4');}, 4000);
functions$.next(f);
f = () => setTimeout(() => {console.log('f5');}, 5000);
functions$.next(f);
setBuffer(8000);
f = () => setTimeout(() => {console.log('f6');}, 6000);
functions$.next(f);
f = () => setTimeout(() => {console.log('f7');}, 7000);
functions$.next(f);
setBuffer(16000);



回答2:


I started working on a solution with combineLatest but figured that a BehaviorSubject would be a better solution once I put more thought into it.

const { BehaviorSubject } = rxjs;
const { filter } = rxjs.operators;

let finalised$ = new BehaviorSubject(false);

function asyncFunc(time) {
  setTimeout(() => {
      console.log('asyncFunc has resolved');
      if (!finalised$.getValue()) {
        finalised$.next(true);
      }
  }, time);
}

function funcToBuffer(time) {
  finalised$.pipe(filter(finalised => finalised)).subscribe(_ => { // Filter so only fire finalised being true
    setTimeout(() => {
      console.log(time);
    }, time);
  });
}

asyncFunc(3000);

funcToBuffer(1000);
funcToBuffer(2000);
funcToBuffer(4000);
funcToBuffer(5000);

asyncFunc(8000);

funcToBuffer(6000);
funcToBuffer(7000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.2.2/rxjs.umd.min.js"></script>



回答3:


CombineLatest that waits for both obsevables to fire.

const { of, combineLatest } = rxjs;
const { delay } = rxjs.operators;


let obs1$ = of(1).pipe(delay(1000));
let obs2$ = of(2).pipe(delay(2000));

let now = new Date();
combineLatest(obs1$, obs2$).subscribe(([obs1, obs2]) => {
  let ellapsed = new Date().getTime() - now.getTime();
  console.log(`${obs1} - ${obs2} took ${ellapsed}`);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.2.2/rxjs.umd.min.js"></script>


来源:https://stackoverflow.com/questions/52022433/using-rxjs-how-to-buffer-function-calls-until-an-other-async-function-call-has-r

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!