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 map
s, combine
s 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.
I am slightly confused by this description. Does one transaction encompass the processing of an original and all derived events, and a flush occurs only at the end of that entire transaction, or do flushes occur per recomputed observable? The last paragraph states, that
combine
useswhenDoneWith
to prevent glitches, which means it would only execute during the flushes and not inside the regular transaction execution. The first paragraph though says, thatcombine
is part of the event processing that happens inside of transactions.Clicking through the code a little, I found two graph traversals:
I can see that
whenDoneWith
can call intoflushDepsOf
, which seems like it might implement a reverse depth-first graph traversal that would prevent glitches due achieving topological order of the dependency graph.Dispatcher.prototype.pushIt
, which appears to be part of the normal in-transaction processing, seems to implement a forwards breadth-first traversal of the dependency graph, which would not prevent glitches.I have seen some libraries that implement "observer-only" glitch prevention so to speak, where glitches are allowed during change propagation but observers are scheduled to execute only at the end of transactions, meaning after all glitches have been settled, so that glitches that ocurr internally between events never become visible to outside observers. So far, my impression is that bacon.js implements this same phase separation, but then does not limit the "observer" phase to only observers. Instead, it allows regular events with dependencies between them to be part of the second phase, and then implements actual glitch prevention for that second phase only. Is that the case?