6

I have two RxJS subjects, say a and b that I need to combine somehow.

someComboOfAandB.subscribe({aVal, bVal} => console.log("value:", aVal, bVal));

I want to combine them such that if a and b are updated synchronously, the values are delivered together:

a.next(1);
// some code
b.next(2)

// at end of synchronous code / frame:
// value: 1 2

However, if just one value is updated, an update will still be pushed at the same time an update with two new values would be pushed:

a.next(5)

// at end of synchronous code / frame:
// value: 5 2

Is this possible? If it is, how so? Even if it is possible, is it something that should be avoided?

JKillian
  • 18,061
  • 8
  • 41
  • 74
  • http://reactivex.io/documentation/operators/combinelatest.html – Luka Jacobowitz Aug 31 '16 at 21:19
  • @LukaJacobowitz The problem is that two updates would get triggered in the first example above, which I can't have. – JKillian Aug 31 '16 at 21:19
  • How about `withLatestFrom`? Will only emit when one of the streams emit. – Luka Jacobowitz Aug 31 '16 at 21:20
  • Or maybe what you're looking for is `combineLatest(...).debounceTime(...)` – Luka Jacobowitz Aug 31 '16 at 21:22
  • `debounceTime` is interesting, but I'm not sure what sort of guarantees on time I could know. For example, what if the code between `a.next` and `b.next` takes 1ms? 12ms? etc. Maybe a `debounce` paired with `requestAnimationFrame` somehow? – JKillian Aug 31 '16 at 21:31
  • I'm not really familiar with `requestAnimationFrame` but yeah you'd need to know how long the code between `a.next` and `b.next` would take. Although if they are the only two calls that are being made close to each other you could be quite conservative and pass in a large value like e.g. 500ms – Luka Jacobowitz Aug 31 '16 at 21:34

1 Answers1

2

You should be able use a Scheduler to effect the behavior you want:

import "rxjs/add/observable/combineLatest";
import "rxjs/add/operator/map";

import { BehaviorSubject } from "rxjs/BehaviorSubject";
import { Observable } from "rxjs/Observable";
import { asap } from "rxjs/scheduler/asap";

let a = new BehaviorSubject(1);
let b = new BehaviorSubject(2);
let combined = Observable
    .combineLatest(a, b, asap)
    .map((values) => ({ aVal: values[0], bVal: values[1] }));

combined.subscribe(
    ({ aVal, bVal }) => { console.log("value:", aVal, bVal); }
);

a.next(3);
b.next(4);

The above code will output the following:

value: 3 4

If the asap Scheduler is not specified, the output would be:

value: 1 2
value: 3 2
value: 3 4

The RxJS GitHub repo contains some Scheduler documentation.

cartant
  • 57,105
  • 17
  • 163
  • 197
  • According to the docs, `asap` "Schedules on the micro task queue, which uses the fastest transport mechanism available, either ... Web Worker MessageChannel or setTimeout or others." This sounds very promising, I'll give it a try tomorrow – JKillian Sep 01 '16 at 03:26
  • Tried this out in my code, and interestingly `asap` didn't work, but `async` did. I guess I'm not clear on the difference between the two in the context of a browser – JKillian Sep 01 '16 at 17:11
  • Hmm, this is frustrating - sometimes it seems to work, sometimes it doesn't. I have a place in my code base basically as such: `log("start"); a.next(1); log("mid"); b.next(2);` And I get `"start", "subscription update", "mid"`, where the subscriber gets an update synchronously with `a.next`. before `b.next` is called. Other times it works though :/ – JKillian Sep 01 '16 at 18:09
  • What RxJS version are you using? [This PR](https://github.com/ReactiveX/rxjs/pull/1820) was merged in 5.0.0-beta.11: "The AsapScheduler and AnimationFrameScheduler were totally busted" – cartant Sep 01 '16 at 20:09
  • You're exactly right - was using `5.0.0-beta.6`. Thanks for the quality answer and insight about the versions! – JKillian Sep 02 '16 at 04:40