Порядок выполнения реактивных реакций имеет свои особенности, которые часто оставляют на волю случая. Однако давайте рассмотрим все возможные варианты…
📰 Subscribe: По времени подписки
🧨 Event: По времени возникновения события
📶 Deep: По глубине зависимости
👨💻 Code: По позиции в программе
📰 Subscribe: По времени подписки
Та реакция, которая появилась раньше, срабатывает раньше. В любом нетривиальном приложении список реакций меняется со временем, а значит, они могут расположиться практически в любом порядке.

В результате получается скрытое состояние, которое влияет на работу приложения через разный порядок побочных эффектов, что может приводить к различным взаимным помехам. Мы получаем нестабильное поведение, что усложняет отладку и тестирование.
Это типичная проблема большинства библиотек.
🧨 Event: По времени возникновения события
Предположим, нам удалось зафиксировать порядок подписок тем или иным способом. Однако есть еще один источник нестабильности — порядок действий.

Явно или неявно изменяя состояния в разном порядке, мы снова можем получить разные порядки реакций. К сожалению, большинство библиотек подвержены и этой проблеме.
📶 Deep: По глубине зависимости
Некоторые библиотеки используют так называемую топологическую сортировку графа, чтобы пересчитывать инварианты в оптимальном порядке — от менее зависимых к более зависимым.

В этом примере Post изменяется на тот, к которому у нас нет доступа. Сначала обновится содержимое страницы, что не только приведет к ненужным пересчетам, но и может вызвать ошибки или просто мусор в виде побочных эффектов. И только затем, при вычислении Page, выяснится, что PostPage должен быть полностью уничтожен, а вместо него должно отображаться сообщение об ошибке доступа Forbidden.
Обратите внимание, что существование Title и Body зависит от значения Page. Но сами значения Title и Body уже не зависят от значения Page. И наоборот, значение Page не зависит от значений Title и Body. То есть связь между ними не реактивна. Но она есть. И это уже связь между владельцем и имуществом. То есть значение Page владеет реактивными состояниями Title и Body и, следовательно, контролирует их жизненный цикл.
Эту проблему нельзя решить, анализируя только реактивный граф. Если только не дополнить его графом владения. Но это потребует еще большей сложности в логике выполнения. И я не уверен, что топологическая сортировка такого двойного графа может быть выполнена с приемлемой алгоритмической сложностью. Иначе вся наша борьба за эффективность будет работать медленнее, чем гораздо более простая, но глупая архитектура.
👨💻 Code: По позиции в программе
Предпочтительно, чтобы реакции всегда обрабатывались в порядке, соответствующем порядку инициализации. Это гарантирует, что владелец обновится раньше всего, чем он владеет.

Здесь сначала обновится Allow, затем Page, что приведет к потере PostPage и, как следствие, к уничтожению PostPage со всеми внутренними состояниями без их вычисления.
🐵 Стабильность порядка вычислений
После изменения состояния результат должен быть таким же, как если бы начать с нуля в том же состоянии. В противном случае фактическое поведение у пользователя может отличаться от того, на котором разработчик ведет отладку.

Это кажется очевидным, но вы будете в ужасе, когда узнаете, что стабильность поведения почти никогда не гарантируется. В результате возможна ситуация, когда программист взял тот же код, открыл те же окна, ввел те же значения… но у пользователя возникает ошибка, а программист не может ее воспроизвести. И тогда начинается веселая отладка.
Поэтому в $mol_wire уделено особое внимание не только корректности, но и предсказуемости порядка вычислений. Это позволяет не бояться побочных эффектов даже в сильно динамичных приложениях, не теряя согласованности состояний. При этом код остаётся простым и элегантным:
class App extends Object { @mem Post( next?: Post ) { return next } // mutable state @mem PostPage() { // object factory return PostPage.make({ Post => this.Post() }) } @mem Forbidden() { // object factory return Forbidden.make({}) } @mem Allow() { // derived state return Guard( this.Post() ) } @mem render() { // recursive side effect if( this.Allow() ) this.PostPage().render() else this.Forbidden().render() } }
Тут метод render всегда выполнится раньше рендера вложенных страниц, какие бы у них ни были зависимости. Но выполнится он только если значение Allow поменяется. Как это работает под капотом поговорим в другой раз.
А пока, подписывайтесь на что-нибудь, вступайте во что-то там, и держите руку на пульсе вот этого вот.
