paldepind / flyd

The minimalistic but powerful, modular, functional reactive programming library in JavaScript.
MIT License
1.56k stars 85 forks source link

Scan behavior with initially empty stream #91

Open jayrbolton opened 8 years ago

jayrbolton commented 8 years ago

I hit a snag in the default behavior of the scan function; in particular, how it pushes the initial accumulator value to the resulting stream immediately if the stream you pass in has no values when starting out (this line)

Here is my specific problem case. This is a countdown timer stream that gets triggered by button clicks:

let clicks$ = flyd.stream()
let countdown$ = flyd.flatMap(ev=> {
  let seconds$ = flyd.scan(n => n - 1, 5, flyd_every(1000))
  let end$ = flyd_filter(n => n < 0, seconds$)
  return flyd.endsOn(end$, seconds$)
}, clicks$)

flyd.map(n => console.log(n), countdown$)
clicks$('click!')
// Incorrectly logs 4, 5, 5, 3, 2, 1, 0

The "4,5,5" values on the stream all get emitted immediately/simultaneously. (I'm not sure why 5 gets emitted twice)

Here is an alternate implementation of scan that does not push the initial accumulator, but you could instead use immediate to get that behavior:

flyd.scan = function(f, acc, s) {
  return flyd.combine(function(s) {
    var val = s()
    acc = val ? f(acc, val) : acc
    return acc
  }, [s]);
};

// Example:
var s = flyd.stream();
var count = flyd.scan((sum, n) => sum + n, 10, s)
count() // returns undefined
flyd.immediate(count)() // returns 10
s(1)
count() // returns 11
flyd.immediate(count)() // returns 11

When using the above scan in my countdown timer, I get 4, 3, 2, 1, 0 emitted as expected with my countdown timer.

yosbelms commented 7 years ago

I have the same problem, after add a subscriber to a stream, it is called immediately. my code looks like the following:

var todos  = flyd.stream([])
var filter = flyd.stream('')

flyd.on(renderView, todos)  // calls render
flyd.on(renderView, filter) // calls render

renderView() // official render call

I've solved the problem by combining streams but it is bit hacky

var todos  = flyd.stream([])
var filter = flyd.stream('')

var allstreams = flyd.combine(function(){}, [todos, filter])

flyd.on(function(){
    flyd.on(renderView, todos)
    flyd.on(renderView, filter) 
},
// fires view render once
allstreams(null))