如何使用Om + Figwheel + core.async编写可重载的异步代码?

4

我想编写一个类似时钟应用的东西。状态基本上是一个不断增加的数字。可以在这里看到一种实现方式。

(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"))}))

我对此的问题是这不是可重新加载的代码。一旦我通过fig wheel刷新代码,递增速度会变得更快,因为有几个正在更新状态的东西。
我尝试了各种想法(基本上是使不同的组件拥有go语句代码),但我无法想出可以工作的东西。
有人有一个简洁的解决方案吗?还是我只能在开发过程中坚持下去?

传统的做法是确保为您的 Om 组件编写卸载事件,以便进行良好的清理。这意味着需要以不同的方式构建组件结构。 - Charles Duffy
...也就是说:在IWillMount中启动例程,在IWillUnmount中关闭它。 - Charles Duffy
@CharlesDuffy 嘿,查尔斯。谢谢你的建议。我试过了,但部分失败了,因为我不确定如何“撤销”通道。此外,我还希望有更聪明的解决方法。 - Tomas Svarovsky
典型的方法是让循环从超时或关闭通道中读取[使用alts!或类似方法]。如果关闭通道关闭了,那么你就退出循环,在IWillUnmount中关闭它。 - Charles Duffy
2个回答

2

您必须告诉goroutine何时停止运行。最简单的方法是发送close!来告诉goroutine:

(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))

任何在循环中运行的goroutine都需要通过figwheel的:on-jsload配置来信号停止。
;; project.clj
(defproject ;; ...
  :figwheel {:on-jsload "myproject.core/on-reload"}
)

最好将长时间运行的 goroutines 视为需要管理的资源。在 Golang 中,常见的模式是将长时间运行的 goroutines 视为进程/墓碑以确保适当的拆除。对于 core.async 的 goroutines 也应该采用相同的方法。


当你说“重新加载时发出停止信号”时,你是指通过figwheel.client/start {:on-jsload}函数吗? - Odinodin
这听起来像是我需要的。不幸的是,这段代码似乎对我无效。它是否依赖于某个特定版本的ClojureScript?我正在运行Chestnut,我认为它并不完全是最新版本。 - Tomas Svarovsky
并不是那段确切的代码使其工作。我用更准确的代码更新了答案,以及你需要在project.clj中配置的内容,让figwheel正常运行。 - Jeff

0

好的。在阅读了建议之后,我尝试着自己实现了一些东西。我不能声称这是最好的解决方案,所以欢迎反馈,但它似乎可以工作。基本上它做了Charles建议的事情。我将其包装在一个组件中,该组件具有添加或删除组件本身时的回调。无论如何,我认为使用figwheel onload hook很难做到这一点。

alts!用于从2个通道获取输入。当组件从DOM中“删除”时,它会向alts!发送:killed信号,退出循环。

clock-controller不渲染任何内容,它基本上只是保持时钟运转并更新应用程序状态,然后可以通过光标由任意其他组件消耗。

(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 [_])))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接