kefirjs / kefir

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

constant stream + concat #181

Closed fernandogmar closed 8 years ago

fernandogmar commented 8 years ago

Hi Roman,

I am new with Kefirjs, so not sure if the behaviour is wrong or just I am doing something wrong...

The idea it is we have an initial state that we would like to emit at the beguinning of the the stream, I have tried with a constant stream... but it doesn't work as I expected

I have done a work around getting the result I expected, using the sequentially method:

var initial_state = Kefir.sequentially(0, [{counter: 0, odd: false}]);
var states = Kefir.sequentially(100, [{counter: 1, odd: true}, {counter:2, odd: false}, {counter:3, odd:true}]);
var state = Kefir.concat([initial_state, states]);
state.map(state => state.counter).onValue(function(value){console.log(value)});
state.map(state => state.odd).onValue(function(value){console.log(value)});

This is the expected result we get:

0
false
1
true
2
false
3
true

Using a constant stream instead:

const initial_state = Kefir.constant({counter: 0, odd: false});
var states = Kefir.sequentially(100, [{counter: 1, odd: true}, {counter:2, odd: false}, {counter:3, odd:true}]);
var state = Kefir.concat([initial_state, states]);
state.map(state => state.counter).onValue(function(value){console.log(value)});
state.map(state => state.odd).onValue(function(value){console.log(value)});

It is not giving me the result I expected (showed before with sequentially):

0
1
true
2
false
3
true

Notice we are not getting the value of the odd property on the console for the initial state.

Am I missing something? or can we say I have found a good bug? :D

Thanks for this wonderful library, I am loving it!

rpominov commented 8 years ago

Hi there. This is a very common pitfall people step on unfortunately.

So the problem is that by applying concat you're converting a Property with current value (constant) to a Stream with current value (concat always returns a stream).

In this context "current value" is a value you get right after subscribing to a stream or property (in response to subscribing).

So the problem here is that you then subscribe twice, and the value emitted in response to first subscription, but by the time you add a second subscription value is already gone.

This problem also explained in docs http://rpominov.github.io/kefir/#current-in-streams

fernandogmar commented 8 years ago

Ok, I think I understood. So the solution would be something as this:

const initial_state = Kefir.constant({counter: 0, odd: false});
var states = Kefir.sequentially(100, [{counter: 1, odd: true}, {counter:2, odd: false}, {counter:3, odd:true}]);
var state = Kefir.concat([initial_state, states]).toProperty();
state.map(state => state.counter).onValue(function(value){console.log(value)});
state.map(state => state.odd).onValue(function(value){console.log(value)});

That gives us the expected result, great!:

0
false
1
true
2
false
3
true

Ok just one last thing... why if concat return a stream in the first workaround:

var initial_state = Kefir.sequentially(0, [{counter: 0, odd: false}]);
var states = Kefir.sequentially(100, [{counter: 1, odd: true}, {counter:2, odd: false}, {counter:3, odd:true}]);
var state = Kefir.concat([initial_state, states]);
state.map(state => state.counter).onValue(function(value){console.log(value)});
state.map(state => state.odd).onValue(function(value){console.log(value)});

why does it give us the expected result?... could it be because this is fired later asynchronously... maybe it uses a setTimeout, is that the reason?

Thanks a lot! I am becoming a pro on Kefirjs ;)

rpominov commented 8 years ago

Yeah, adding .toProperty() is one of valid ways to fix this.

why does it give us the expected result?... could it be because this is fired later asynchronously...

You are right, it's because with Kefir.sequentially(0, [x]) you create a stream that fires a value asynchronously, in response to first subscribe but still asynchronously (using setTimeout(fn, 0)). So the second subscriber has a chance to subscribe before value emitted.