Skip to content

Транзакции и время ​

Каждое обновление в Continuum происходит внутри транзакции — одного атомарного момента логического времени. e.fire(x) открывает момент; всё, что следует из x, устаканивается внутри него; момент закрывается; только после этого эффекты (listen, патчи DOM) видят результат.

Отсутствие глитчей ​

Если одно событие питает две ветки, которые позже сходятся («ромб»), точка слияния пересчитывается один раз за момент, видя обе ветки обновлёнными:

ts
const doubled = count.map((n) => n * 2);
const squared = count.map((n) => n * n);
const sum = combine(doubled, squared, (d, s) => d + s);
// count: 2 → 3 переводит sum 8 → 15 одним шагом. «6 + 4» не существует.

Внутри узлы ранжированы по глубине графа и обрабатываются в порядке рангов, так что узел не сработает раньше, чем устаканятся его входы. Вы этим не управляете — это и есть смысл слова «транзакция» здесь.

Одновременность — настоящая ​

Два вхождения в одном моменте действительно одновременны, и API заставляет сказать, что это значит:

  • Stream.merge(ea, eb, combine) — одновременные вхождения коалесцируются функцией combine, а не упорядочиваются как попало;
  • at видит States на начало момента.

Задержка hold ​

hold/accum обновляют States на границе момента. Внутри транзакции, доставившей вхождение, State ещё показывает предыдущее значение:

ts
const count = root(() => clicks.accum(0, (_e, n) => n + 1)); // модульному состоянию нужен владелец
const before = count.at(clicks);
// на 3-м клике `before` равно 2 — значение ДО этого момента

Именно это делает рекурсивные определения корректными: «новое значение зависит от старого» требует «старого», стабильного внутри момента. Прошлое доступно; настоящее ещё формируется.

Пакет из нескольких fire ​

Каждый fire/set обычно открывает собственный момент. Когда одно действие пользователя обновляет несколько источников, оберните их в batch — всё внутри войдёт в один момент: узлы-соединения пересчитаются один раз, merge скоалесцирует, наблюдатели сработают однажды в конце:

ts
import { batch } from "@continuum-js/frp";

batch(() => {
  query.set("");
  page.set(1);
  selection.set(null);
}); // один момент: один пересчёт на соединение, один патч DOM на привязку

Вложенный batch присоединяется к объемлющему моменту. Нужен он редко — выведенное состояние обычно избавляет от необходимости сеттить несколько источников разом, — но когда сеттите несколько, batch не даёт промежуточным состояниям существовать вовсе.

Одно правило: у потока не больше одного вхождения за момент, поэтому двойной fire одного и того же потока внутри batch бросает ошибку (молча свернуть второе вхождение поверх до-моментного состояния — значит испортить accum). Повторный set одного state легален — побеждает последняя запись, а updates доставит подписчикам одно коалесцированное вхождение с финальным значением.

Эффекты — после ​

Обработчики listen выполняются в фазе post, после закрытия момента. Если обработчик порождает новое событие (редирект роутера, ответ perform), оно открывает новый момент — транзакции не вкладываются и не переплетаются.

Полный операционный разбор — три фазы, ранги, switch и соответствие книге о Sodium — в FRP-MODEL.


Незнакомый термин? Вся терминология этой документации объяснена в глоссарии.