0no-co / wonka

🎩 A tiny but capable push & pull stream library for TypeScript and Flow
MIT License
709 stars 29 forks source link

How to avoid intermediary values when combine uses the same input sources twice? #107

Closed artalar closed 3 years ago

artalar commented 3 years ago

Is it possible to avoid glitches with synchronous receiving data from a few sources?

import w from 'wonka'

const log = []
const s = w.makeSubject()
w.pipe(
  w.combine(s.source, s.source),
  w.subscribe(v => log.push(v)),
)

s.next(0)
s.next(1)

console.assert(
  JSON.stringify(log) === JSON.stringify([ [0, 0], [1, 1] ]) // false
)

In example above the log will be [[0,0],[1,0],[1,1]], where [1,0] is redundant data (glitch)

kitten commented 3 years ago

that isn't a glitch. combine gives you the combined data on each update, meaning that if you combine two sources that fire at the same time, it'll yield two events, since combined tupel outputs contain each update. So to avoid this you have multiple options, depending on your use-case and exact sources. But for instance for the small example you've posted this works:

pipe(
  combine(subject.source, subject.source),
  sample(subject.source),
  subscribe(console.log),
)

And this works because the sample operator takes the latest value it sees and only emits it when the source it receives emits. Since the subject will only be emitting once and the sample operator is processed after combine this discards the intermediate tupel.

artalar commented 3 years ago

@kitten thx for so fast feedback (wow! =D)

It is a good local (manual) solution, but I want to find automatically solution, how do you think, it is possible with wonka? Typically it solved by topological sorting, but there are other options.

kitten commented 3 years ago

I'm not sure what you mean by automatic? Schedulers on sources like these are basically just output-bound, so if we suppose that each event has a certain timing after each source a scheduler alters when and how an event is issued.

In Wonka all events are issued synchronously which has a lot of benefits in terms of predictability. In never ended up adding operators that apply back pressure in more interesting ways or schedules events in more complex ways, but generally the operators around back pressure that do exist are debounce, throttle, and buffer. A good example of scheduling alterations is delay.

But ultimately none of this would never be automatic or implicit, since scheduling always includes an intent to process events differently. That being said, the highly synchronous nature of sources in Wonka is intentional and the most predictable manner in which events are issued and processed.

artalar commented 3 years ago

Here I make a demo with a performance comparison of wonka, effector, reatom: https://runkit.com/artalar/5ffc6163d282b40013440110

To avoid glitches:

I don't why, but Wonka is 2-3 times slower. Have you interested in why? I try to replace locally w_combine (with sample) by native combine and get execution median from 2-3 times to ~1.2 times bigger than Effector and Reatom (which is steel looks a lot, but better than earlier) and result value (wRes) is 7 times bigger, that show to us a possible glitchs impact. It is ok by the perf test because wD, wF, wG, wH, mapper functions are simple and make low-cost computation, so we may rerun it a few times without perf overhead, but as we don't know how heavy is a mapper function in some user code the case of this discussion is how to avoid glitches and make it with less perf cost, any ideas?