Как я могу написать перезаписываемый асинхронный код с помощью Om + Figwheel + core.async?
Я хотел бы написать что-то вроде приложения часов. Состояние в основном число, которое многократно увеличивается. Один из способов сделать это можно увидеть здесь.
(ns chest-example.core
(:require [om.core :as om :include-macros true]
[om.dom :as dom :include-macros true]
[cljs.core.async :as async])
(:require-macros [cljs.core.async.macros :refer [go]]))
(defonce app-state (atom {:time 0}))
(defn clock-view [data owner]
(reify
om/IRender
(render [_]
(dom/div nil (pr-str data)))))
(go (while true
(async/<! (async/timeout 1000))
(om/transact! (om/root-cursor app-state) :time inc)))
(defn main []
(om/root
clock-view
app-state
{ :target (. js/document (getElementById "clock"))}))
Проблема у меня с этим, что это не перезагружаемый код. Как только я обновляю код через фиговое колесо, инкремент становится быстрее, поскольку состояние обновляется несколькими способами.
Я пытался экспериментировать с различными идеями (в основном создавая разные компоненты для собственного кода оператора go), но я не смог придумать что-то, что сработало бы.
У кого-нибудь есть подходящее решение для этого, или мне просто нужно придерживаться его во время разработки?
2 ответа
Вы должны сказать goroutine, когда прекратить бежать. Самый простой способ сделать это - отправить close!
сказать горутину:
(ns myproject.core
;; imports
)
(def my-goroutine
(go-loop []
(when (async/<! (async/timeout 1000))
(om/transact! (om/root-cursor app-state) :time inc)
(recur)))))
;; put in your on-reload function for figwheel
(defn on-reload []
(async/close! my-goroutine))
Любая процедура, которая выполняется в цикле, должна быть сигнализирована для остановки при перезагрузке (через figwheel's :on-jsload
конфигурации).
;; project.clj
(defproject ;; ...
:figwheel {:on-jsload "myproject.core/on-reload"}
)
Лучше всего относиться к долгосрочным процедурам как к ресурсу, которым нужно управлять. В golang это обычная схема, при которой долгоиграющие программы воспринимаются как процессы / надгробия для обеспечения надлежащего демонтажа. То же самое следует применять к процедурам core.async.
Хорошо. После прочтения предложений я попытался реализовать что-то сам. Я не могу утверждать, что это лучшее решение, поэтому обратная связь приветствуется, но, похоже, работает. В основном это делает то, что предложил Чарльз. Я обернул его внутри компонента, который имеет обратные вызовы, когда сам компонент добавляется или удаляется. Я думаю, что это было бы трудно сделать с крюком загрузки figwheel в любом случае.
Альты! используется, чтобы мы могли получить вход от 2 каналов. Когда компонент "удаляется" из DOM, он посылает сигнал:kill альтам! который выходит из цикла.
Контроллер часов ничего не отображает, он в основном просто для того, чтобы часы тикали и обновляли состояние приложения, которое затем может быть использовано через курсор произвольными другими компонентами.
(defn clock-controller [state owner]
(reify
om/IInitState
(init-state [_]
{:channel (async/chan)})
om/IWillMount
(will-mount [_]
(go (loop []
(let [c (om/get-state owner :channel)
[v ch] (async/alts! [(async/timeout 1000) c])]
(if (= v :killed)
nil
(do
(om/transact! state :time (fn [x] (+ x 1)))
(recur)))))))
om/IWillUnmount
(will-unmount [_]
(let [c (om/get-state owner :channel)]
(go
(async/>! c :killed)
(async/close! c))))
om/IRender
(render [_])))