maybe-then with p/letStatus: done Priority: P2 Created: 2026-02-11 Completed: 2026-02-12 Owner: AI
v20150901_async_impl.cljc defines a maybe-then primitive that chains possibly-async operations. In many functions, this leads to deeply nested callback pyramids — up to 6 levels deep with closing parens like )))))))))))))))))))))))) (line 591). The promesa p/let macro provides sequential binding syntax that flattens these chains while preserving the same semantics.
p/let always returns a promise (even when all bindings are synchronous). This is acceptable because:
v20150901_impl.cljc) exists for zero-overhead syncmaybe-then, which correctly handles promises via p/thendo-sequence already uses p/let internally (line 82), so this is consistentmaybe-then chains (2+ levels) with p/let bindingsmaybe-then for single-step transforms and conditional/branching patternsmaybe-chain, defined but never called)src/main/com/fulcrologic/statecharts/algorithms/v20150901_async_impl.cljc — main refactoring targetUse p/let for sequential chains, keep maybe-then for single transforms and conditional recursion.
1. send! (lines 384-408) — highest impact
5-level nested maybe-then for independent values → flat p/let:
;; Before: 25 lines of nesting
(maybe-then event-name-v (fn [event-name]
(maybe-then target-v (fn [target]
(maybe-then type-v (fn [type]
(maybe-then delay-v (fn [delay]
(maybe-then content-v (fn [content] ...))))))))))
;; After:
(p/let [event-name event-name-v
target target-v
type type-v
delay delay-v
content content-v]
(let [target-is-parent? (= target (env/parent-session-id env))
data (merge (named-data env namelist) content)]
(when idlocation (sp/update! data-model env {:ops [(ops/assign idlocation id-v)]}))
(sp/send! event-queue env ...)))
2. enter-states! inner per-state body (lines 551-591) — highest impact
6-level nested maybe-then with 25 closing parens → sequential p/let:
;; After:
(p/let [_ late-init
_ (do-sequence (chart/entry-handlers statechart s)
(fn [entry] (execute! env entry)))
_ (when-let [t (and (contains? states-for-default-entry s)
(some->> s (chart/initial-element statechart) (chart/transition-element statechart)))]
(execute! env t))
_ (when-let [content (get default-history-content (chart/element-id statechart s))]
(execute-element-content! env (chart/element statechart content)))]
;; Final state handling (pure data operations + one possible async)
(when (chart/final-state? statechart s)
(if (= :ROOT (chart/get-parent statechart s))
(vswap! vwmem assoc ::sc/running? false)
(p/let [done-data (compute-done-data! env s)]
(vswap! vwmem update ::sc/internal-queue conj ...)
(when (and (chart/parallel-state? statechart grandparent) ...)
(vswap! vwmem update ::sc/internal-queue conj ...))))))
3. process-event! body (lines 1117-1136)
4-level nested → flat:
(p/let [_ (select-transitions! env event)
_ (handle-external-invocations! env event)
_ (microstep! env)
_ (before-event! env)]
(vswap! vwmem dissoc ::sc/enabled-transitions ::sc/states-to-invoke ::sc/internal-queue)
(env/assign! env [:ROOT :_event] nil)
@vwmem)
4. initialize! (lines 1164-1187)
4-level nested → flat:
(p/let [_ early-init
_ (when (map? invocation-data)
(sp/update! data-model env {:ops (ops/set-map-ops invocation-data)}))
_ (enter-states! env)
_ (before-event! env)
_ (when script (execute! env script))]
(vswap! vwmem dissoc ::sc/enabled-transitions ::sc/states-to-invoke ::sc/internal-queue)
@vwmem)
5. microstep! (lines 899-908)
2-level nested → flat:
(p/let [_ (exit-states! env)
_ (execute-transition-content! env)]
(enter-states! env))
6. exit-interpreter! inner (lines 1032-1040)
2-level nested → flat:
(p/let [_ (run-exit-handlers! env state)
_ (cancel-active-invocations! env state)]
(vswap! vwmem update ::sc/configuration disj state)
(when (and (not skip-done-event?) (chart/final-state? statechart state) (= :ROOT (chart/get-parent statechart state)))
(send-done-event! env state)))
7. exit-states! inner per-state (lines 882-893)
2-level nested → flat:
(p/let [_ (run-many! env to-exit)
_ (cancel-active-invocations! env s)]
(vswap! vwmem update ::sc/configuration disj s))
8. before-event! (lines 1048-1066)
3-level nested → p/let for the inner chain:
(p/let [_ (handle-eventless-transitions! env)]
(if (-> vwmem deref ::sc/running?)
(p/let [_ (run-invocations! env)]
(when (seq (::sc/internal-queue @vwmem))
(step)))
(exit-interpreter! env)))
9. invocation-details (lines 750-758)
2-level nested → flat:
(p/let [type (or type-v :statechart)
src src-v]
(assoc invocation :type type :src src
:processor (first (filterv #(sp/supports-invocation-type? % type) invocation-processors))))
10. start-invocation! (lines 760-806)
Nested details + param-result chains → p/let.
maybe-then for:condition-match (line 159), compute-done-data! (line 525)handle-eventless-transitions! recursive loophandle-external-invocations! finalize/forward chainfind-first-matching-transition, find-enabled-transition-for-state, select-transitions*maybe-chain (lines 59-67) — defined but never calledv20150901-async.async-spec (33 tests, 99 assertions)maybe-chain removed, no remaining referencesmaybe-then nesting deeper than 1 level (single transforms only).cljc file loads cleanly; no CLJS-specific code was modified)Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |