kefirjs / kefir

A Reactive Programming library for JavaScript
https://kefirjs.github.io/kefir/
MIT License
1.87k stars 97 forks source link

Combine two streams and emit value whenever at least one source stream emitted. #171

Closed belfz closed 8 years ago

belfz commented 8 years ago

Hello.

I've got two streams that I would like to combine, but I want the resulting stream to start emitting values as soon as at least one of the sources emitted the values.

The docs for combine method say this: The result stream emits a value only when it has at least one value from each of source observables.

Hence I can't use that. Is there any possible way to achieve what I want?

I've got an example on codepen. I want the counter to increment even if only one of the divs (A or B) was clicked. Currently, it only increments when both of them were clicked at least once.

Thank you.

rpominov commented 8 years ago

Hi, one way to do this is by using merge and scan:

let combined = Kefir.merge([
   a.map(a => ({b}) => ({a, b})),
   b.map(b => ({a}) => ({a, b}))
 ]).scan((r, fn) => fn(r), {a: null, b: null})

Not sure it's the simplest one though.

rpominov commented 8 years ago

Another variation of this using Ramda.merge

let combined = Kefir.merge([
  a.map(a => ({a})),
  b.map(b => ({b}))
]).scan(R.merge, {a: null, b: null})
belfz commented 8 years ago

Thank you. Indeed, these solutions are not the simplest. Did you consider adding such method (with simpler syntax, ideally similar to combine method) to the API?

rpominov commented 8 years ago

Hm, not sure yet it's a good idea. I've never needed this feature. Also, as far as I know, other FRP libs don't have it either. What is your real world use case? Because the example with summation of two counters can be done without any combine at all:

Kefir.merge([a, b]).scan(r => r + 1, 0)
rpominov commented 8 years ago

Oh, there is a simpler solution:

let combined = Kefir.combine([
  a.toProperty(() => 0),
  b.toProperty(() => 0)
], (a, b) => a + b)

You just turn streams to properties so they always have some initial value.

rpominov commented 8 years ago

Oh, and scan() already returns a property, you just need to use seed and your original example should work:

- let a = Kefir.fromEvents(document.querySelector('.a'), 'click').map(e => 1).scan((t,c)=>t+c)
+ let a = Kefir.fromEvents(document.querySelector('.a'), 'click').map(e => 1).scan((t,c)=>t+c, 0)

I think having .toProperty(() => 0) as a general solution for this problem was the original reasoning for not implementing this feature.

belfz commented 8 years ago

Perfect. I think that the solution with using .toProperty() is what I was looking for. Thanks again for your help!