简体   繁体   中英

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

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.

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$ , ie 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);

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>

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>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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