clojurescript 2025-07-05

Inside of a donut.system <https://donut.party/docs/system/> component's :start function I need to do some asynchronous work (i.e. await promise(s)). In the JS prototype of this app I have a top-level async function that (through calling various application start-up functions) eventually does an await LedgerTransportHid.create() then uses the result to perform other start-up steps. In the CLJS code I am struggling to get this asynchronous setup working. Specifically, I am struggling to get the promise(s) to be resolved/rejected, they are always pending. Code in ๐Ÿงต thread.

(ns my.main
  (:require
    [clojure.pprint :refer [pprint]]
    [donut.system :as ds]
    [my.systems :as systems]))


(def *running-system (atom nil))
(defn start-system ([system & args] (reset! *running-system (apply ds/start system args))))
(defn stop-system ([& args] (reset! *running-system (apply ds/stop @*running-system args))))

(defn -main
  ([& _args]
   (pprint (systems/system))
   (start-system (systems/system))
   (stop-system)))

(ns my.systems
  (:require
   [clojure.pprint :refer [pprint]]
   [promesa.core :as p]
   ["@ledgerhq/hw-transport-node-hid" :default LedgerTransportHid-js]
   ["@ledgerhq/hw-app-eth" :default LedgerEthApp-js]
   [donut.system :as ds]))


(defonce ^:private LedgerTransportHid (js->clj LedgerTransportHid-js))
(defonce ^:private LedgerEthApp (js->clj LedgerEthApp-js))


(defn- hid-transport-start
  ([{::ds/keys [config]}]
   (pprint {['hid-transport ::ds/start 'config] config})
   (p/let [instance (.create LedgerTransportHid)]
     (pprint {['hid-transport ::ds/start 'config] config
              ['hid-transport ::ds/start 'instance] instance})
     instance)))

(defn- hid-transport-stop
  ([{::ds/keys [config instance]}]
   (pprint {['hid-transport ::ds/stop 'config] config
            ['hid-transport ::ds/stop 'instance] instance})
   instance))

(defn- hid-transport
  ([]
   #::ds{:config {}
         :start hid-transport-start
         :stop hid-transport-stop}))


(defn- eth-app-start
  ([{::ds/keys [config]}]
   (pprint {['eth-app ::ds/start 'config] config})
   (p/let [instance (LedgerEthApp. (:transport config))]
     (pprint {['eth-app ::ds/start 'config] config
              ['eth-app ::ds/start 'instance] instance})
     instance)))

(defn- eth-app-stop
  ([{::ds/keys [config instance]}]
   (pprint {['eth-app ::ds/stop 'config] config
            ['eth-app ::ds/stop 'instance] instance})
   instance))

(defn- eth-app
  ([]
   #::ds{:config {:transport (ds/local-ref [:transport])}
         :start eth-app-start
         :stop eth-app-stop}))


(defn system
  ([]
   #::ds{:defs
         {:ledger {:transport (hid-transport)
                   :eth-app (eth-app)}}}))

Here is the output of running the above code (the -main function):

{:donut.system/defs
 {:ledger
  {:transport
   {:donut.system/config {},
    :donut.system/start #object[hid_transport_start],
    :donut.system/stop #object[hid_transport_stop]},
   :eth-app
   {:donut.system/config {:transport [:donut.system/local-ref [:transport]]},
    :donut.system/start #object[eth_app_start],
    :donut.system/stop #object[eth_app_stop]}}}}

{[hid-transport :donut.system/start config] {}}

{[eth-app :donut.system/start config] {:transport #<Promise[pending:65]>}}

{[eth-app :donut.system/stop config] {:transport #<Promise[pending:65]>},
 [eth-app :donut.system/stop instance] #<Promise[pending:78]>}

{[hid-transport :donut.system/stop config] {},
 [hid-transport :donut.system/stop instance] #<Promise[pending:65]>}

And the deps.edn's :deps (which are brought in via shadow-cljs's :deps true):

org.clojure/clojure       {:mvn/version "1.12.1"}
org.clojure/clojurescript {:mvn/version "1.12.42"}
org.clojure/core.async    {:mvn/version "1.8.741"}
funcool/promesa           {:mvn/version "11.0.678"}
party.donut/system        {:mvn/version "1.0.255"}

I'd try asking in #donut.

I did not realize such a channel existed, thank you!

Anyone can create channels here, and authors of libraries often do. :)

๐Ÿ‘ 1