Как я могу написать перезаписываемый асинхронный код с помощью 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 [_])))
Другие вопросы по тегам