Mithril.js: Mithril stream.end(true) ends other dependent streams as well

Created on 2 Jun 2020  ·  12Comments  ·  Source: MithrilJS/mithril.js

**Mithril version: 2.0.4

Browser and OS: Windows 10, Chrome Version 83.0.4103.61


Project:

mithril stream vs flyd stream

credit to @foxdonut for the reproduction

notice in the mithril example how o2 doesn't receive the false value
but using the same example with flyd, o2 does receive the false value

The code I was writing was this:

    if (auth.refreshing()) {
      const waiting = auth.refreshing.map((val) => {
        if (val === false) {
          resolve(token())
          waiting.end(true)
        }
      })
    }

The unexpected behaviour i experience is that when there were X numbers of waiting streams the code waiting.end(true) seemed to end ALL of them

Bug

Most helpful comment

@isiahmeadows the two links above ("mithril stream" and "flyd stream") are Flems showing the issue.

All 12 comments

Could you provide a fuller repo of this? Ideally a Flems? It'd be much easier to figure out if it's by design or a proper bug - every stream library's behavior is just the sum of a ton of subtlety.

@isiahmeadows the two links above ("mithril stream" and "flyd stream") are Flems showing the issue.

Yeah, that's a nasty bug. o1 and o2 should either both end or both continue. I'll look into it.

Didn't pay close attention to the links. Sorry!

Just for clarity, here's a minimal example that doesn't involve nesting: link

No worries!

The minimal example doesn't expose the bug though... does it?

@foxdonut It prints the same thing as your Mithril link. I just removed some unnecessary printing.

@isiahmeadows Please have a closer look because it's not the same thing and it does not expose the bug. In my Mithril link, notice that o2 doesn't receive the false value, only o1. That is the bug. In the Flyd link, both o1 and o2 receive the false value.

Oh, whoops. You're right, my bad. 🤦‍♀️

I'm pretty sure this has to do with ending a stream within a dependent stream's map. What's really going is better illustrated here. Note that after o1 gets ended, o2 is missing but o3-5 show results. This is because as the parent stream is executing, the end call unregisters the child and splices the dependent streams/dependent functions _during_ the iteration which causes the skip.

I agree this isn't ideal behavior and probably the simple fix is to mark stream removal, check for stream removal before fn execution, unregister all marked for removal streams _after_ the execution calls.

Thoughts?

@jaydh The actual issue is much simpler: it's skipping all remaining children, even ones that weren't ended, even though they remain subscribed - note that in the subsequent emit after that one, it's still received by both streams. I suspect a return is used where a continue or similar should be used instead.

@isiahmeadows That is not what is happening here. Note that after o1 gets ended in the execution of o2, o3 gets skipped, but o4 and o5 still print results.

This behavior is explained in the source, when invoking refreshing(false) we have the parent stream with

dependentStreams = [o1, o2, o3, o4, o5]
and we run through

dependentStreams.forEach(function(s, i) { s(dependentFns[i](value)) })
o1 runs (index 0),
o2 runs (index 1), but now we call _unregisterChild(o1) which mutates depedentStreams to look like [o2, o3, o4, o5]
and now we're on index 2, which now points to o4 which is why o3 gets skipped and o5 still prints.

Now that I look closer, that's even weirder.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

andraaspar picture andraaspar  ·  4Comments

raykyri picture raykyri  ·  4Comments

StephanHoyer picture StephanHoyer  ·  4Comments

isiahmeadows picture isiahmeadows  ·  4Comments

designMoreWeb picture designMoreWeb  ·  4Comments