Skip to content

Instantly share code, notes, and snippets.

@raimohanska
Last active November 1, 2018 09:37
Show Gist options
  • Save raimohanska/aaa4a6f3cafc92c201eab745f1c71c69 to your computer and use it in GitHub Desktop.
Save raimohanska/aaa4a6f3cafc92c201eab745f1c71c69 to your computer and use it in GitHub Desktop.
Bacon.js glitch prevention

The key to glitch prevention is the UpdateBarrier. This component takes care of starting and finishing event transactions. The inTransaction function is used to wrap all event handling, creating a transaction that lasts until the original event and all derived events (those created when passing the event through all the maps, combines etc) have been processed.

In the end of the transaction, a flush occurs. During flush, all pending actions are performed. And what are these action then? They are actions registered using the whenDoneWith function, and are basically requests like "please run this function when the dependencies certain observable have been fully flushed". To satisfy these request, the UpdateBarrier then executes these delayed actions, from roots to leaves, making sure that the observables get flushed in the correct order, from the roots up. And by roots I mean the observables that have no dependencies on other ones, while leaves are the observables that no other observable depends on. To optimize performance, UpdateBarrier has a double bookkeeping of the pending actions.

Certain combinators, such as the combine* family and takeUntil, (both indirectly though) call the whenDoneWith method to make sure glitch don't occur. In the case of combine, that means emitting only one output event regardless of how many of its dependencies have emitted an event during the transaction. In the case of takeUntil it means that nothing is emitted if both the "source" and "stopper" observables have emitted during the transaction.

@raimohanska
Copy link
Author

raimohanska commented Nov 1, 2018

Flush occurs at the end of the transaction including all the derived events.

The badly named pushIt method doesn't traverse into dependencies, but merely flushes possibly queued events out. This is something that occurs in the scope of a single observable, within a transaction.

I'll try to clarify the overall algorithm below.

  1. When an event is triggered from the "outside", a transaction is started and the event is pushed forward through all the deps all the way to the leaves. Exceptions to this flow include
    1.1 Updates to external subscribers are deferred by the subscribe method, to be flushed after the transaction is complete, in phase 3
    1.2 In some multi-dependent combinators like combine, also internal processing is deferred to phase 2 using whenDoneWith to prevent glitches.
  2. At the end of the transaction, all the "waiters" registered with whenDoneWith are processed in a roots-first order, as described in the Gist.
  3. After this phase is done all the "afters" are processed. This includes flushing to the external subscribers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment