How do i execute the following scenario in the browser with RxJs:
Intermediate solution that i've come up with:
Rx.Observable .fromPromise(submitJobToQueue(jobData)) .flatMap(jobQueueData => Rx.Observable .interval(1000) .delay(5000) .map(_ => jobQueueData.jobId) .take(55) ) .flatMap(jobId => Rx.Observable.fromPromise(pollQueueForResult(jobId))) .filter(result => result.completed) .subscribe( result => console.log('Result', result), error => console.log('Error', error) );
takeUntil
flatMap
usage here semantically correct? Maybe this whole thing should be rewritten and not chained with flatMap
?Starting from the top, you've got a promise that you turn into an observable. Once this yields a value, you want make a call once per second until you receive a certain response (success) or until a certain amount of time has passed. We can map each part of this explanation to an Rx method:
"Once this yields a value" = map
/flatMap
(flatMap
in this case because what comes next will also be observables, and we need to flatten them out)
"once per second" = interval
"receive a certain response" = filter
"or" = amb
"certain amount of time has passed" = timer
From there, we can piece it together like so:
Rx.Observable .fromPromise(submitJobToQueue(jobData)) .flatMap(jobQueueData => Rx.Observable.interval(1000) .flatMap(() => pollQueueForResult(jobQueueData.jobId)) .filter(x => x.completed) .take(1) .map(() => 'Completed') .amb( Rx.Observable.timer(60000) .flatMap(() => Rx.Observable.throw(new Error('Timeout'))) ) ) .subscribe( x => console.log('Result', x), x => console.log('Error', x) ) ;
Once we've got our initial result, we project that into a race between two observables, one that will yield a value when it receives a successful response, and one that will yield a value when a certain amount of time has passed. The second flatMap
there is because .throw
isn't present on observable instances, and the method on Rx.Observable
returns an observable which also needs to be flattened out.
It turns out that the amb
/ timer
combo can actually be replaced by timeout
, like so:
Rx.Observable .fromPromise(submitJobToQueue(jobData)) .flatMap(jobQueueData => Rx.Observable.interval(1000) .flatMap(() => pollQueueForResult(jobQueueData.jobId)) .filter(x => x.completed) .take(1) .map(() => 'Completed') .timeout(60000, Rx.Observable.throw(new Error('Timeout'))) ) .subscribe( x => console.log('Result', x), x => console.log('Error', x) ) ;
I omitted the .delay
you had in your sample as it wasn't described in your desired logic, but it could be fitted trivially to this solution.
So, to directly answer your questions:
interval
will be disposed of the moment the subscriber count drops to zero, which will occur either when the take(1)
or amb
/ timeout
completes.Here's the jsbin I threw together to test the solution (you can tweak the value returned in pollQueueForResult
to obtain the desired success/timeout; times have been divided by 10 for the sake of quick testing).
A small optimization to the excellent answer from @matt-burnell. You can replace the filter and take operators with the first operator as follows
Rx.Observable .fromPromise(submitJobToQueue(jobData)) .flatMap(jobQueueData => Rx.Observable.interval(1000) .flatMap(() => pollQueueForResult(jobQueueData.jobId)) .first(x => x.completed) .map(() => 'Completed') .timeout(60000, Rx.Observable.throw(new Error('Timeout'))) ) .subscribe( x => console.log('Result', x), x => console.log('Error', x) );
Also, for people that may not know, the flatMap operator is an alias for mergeMap in RxJS 5.0.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With