0no-co / wonka

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

Question about onStart execution order #111

Closed ejez closed 3 years ago

ejez commented 3 years ago

In the following example I was expecting "onStart" callback to be invoked first, but noticed it was run last:

https://codepen.io/ejez/pen/NWjWBoJ

const src = fromArray([0, 1]);

const { unsubscribe } = pipe(
  src,

  onStart(() => console.log("onStart...")),

  onPush(() => console.log("onPush...")),

  onEnd(() => console.log("onEnd...")),

  subscribe((result) => console.log(result))
);

result:

"onPush..."
0
"onPush..."
1
"onEnd..."
"onStart..."

From the docs:

When the stream starts then the sink is called with Start, Then for every incoming, new value it’s called with Push('a), and when the stream ends it’s finally called with End.

onStart Run a callback when the Start signal is sent to the sink by the source.

Many thanks.

kitten commented 3 years ago

The fromArray operator synchronously pushes values using a small trampoline scheduler. It cycles through values as long as values are pulled. All sinks, like subscribe start sending Pull signals as soon as they receive Start.

That leaves the detail of when onStart runs. It actually forwards the Start signal before running the callback, which then means that all values are pushed before this function actually runs in this exceptional case.

The reason for that implementation detail is that if onStart ran before Start was forwarded then we run the risk of executing it before the source has fully started. That's a minor gotcha, but it's currently necessary given the risks of Start not fully propagating, since it's often used as a signal to set up other states.

ejez commented 3 years ago

@kitten Thanks for the explanation!

I was using onStart to set a fetching state in urql, and was resetting it in onEnd and when a result is received in subscribe. It was working for the first fetch, but breaks when the result is pushed synchronously from cache afterwards, due to the onStart callback executed last. I was not expecting that and it took me a little bit of time to find about the unexpected execution order.

I hope this issue will be of benefit for other users, but an entry in the docs (time permitting) would be appreciated.

Thanks for the hard work on this library!

kitten commented 3 years ago

Yep, in urql we typically get around this by setting fetching to true before the source is activated/subscribed to. Logically, there are two phases:

The onStart takes care of the second phase, but the first phase should simply be implemented as synchronous/imperative code before using the source.

ejez commented 3 years ago

Thanks!

What i did, is adding a condition for setting fetching to true in onSart (only when there is no data, ie first fetch). I will change it as per your suggestion, as it seems onStart is not the natural place for setting the fetching state.

kitten commented 3 years ago

yea, sounds good 👍 I'll close this for now, since I think the question itself is addressed.

The problems here and ambition is basically always with synchronous streams. The synchronous support in Wonka allows us to apply some nice tricks in urql, but that does come with some caveats. Synchronous events are just very different from asynchronous as it'll always deal with an exact order.