Clojurescript + Om: дождитесь изменения состояния, затем сделайте что-нибудь

Я пытаюсь создать приложение Clojurescript, которое показывает рецепты.

Далее следует соответствующий код (также доступен как суть):

(defn load-recipes [data]
  (go (if (not (:loaded? @data))
        (let [recipes-data (<! (fetch-recipes data))]
          (om/update! data :recipes recipes-data)
          (om/update! data :loaded? true))
        (println "Data already loaded"))))

(defn define-routes [data]
  (defroute home-path "/" []
    (om/update! data :view :home))
  (defroute "/random" []
    (go (loop [loaded? (:loaded? (om/value data))]
          (if-not loaded? (do (println "Waiting for data...")
                              (recur (:loaded? (om/value data))))
                  (do (om/update! data :tag
                                  (rand-nth
                                   (vec (apply set/union (map :tags (:recipes @data))))))
                      (om/update! data :view :random)))))))

(defn app-view [data owner]
  (reify
    om/IWillMount
    (will-mount [_]
      (do
        (load-recipes data)
        (define-routes data)))
    om/IDidMount
    (did-mount [_]
      #_(fetch-recipes data))
    om/IRender
    (render [_]
      (html data))))

Чего я хочу добиться:

  • Сначала получите рецепты с помощью асинхронного http-вызова. Я использую cljs-http.client, который возвращает канал
  • Определите маршруты, используя библиотеку секретаря. В маршруте /random я хочу выбрать случайный рецепт. Это может произойти только тогда, когда данные были получены и обновлены в атоме приложения.

Теперь я получаю бесконечный цикл в браузере. Что творится?

Другой вариант — обернуть все мои маршруты, которым сначала нужно получить данные, в блок go и поместить (<! (load-recipes)) в первую строку.

PS: я закончил с

(defn ensure-recipes-loaded [data]
  (go (if (not (:loaded? (om/value data)))
        (do (om/update! data :view :loading)
            (let [recipes-data (<! (fetch-recipes data))]
              (om/update! data :recipes recipes-data)
              (om/update! data :loaded? true)))
        (println "Data already loaded"))))

(defn define-routes [data]
  (defroute home-path "/" []
    (om/update! data :view :home))
  (defroute "/random" []
    (go
      (<! (ensure-recipes-loaded data))
      (do (om/update! data :tag
                      (rand-nth
                       (vec (apply set/union (map :tags (:recipes @data))))))
          (om/update! data :view :random))))
  (defroute "/random/:tagname" [tagname]
    (go (<! (ensure-recipes-loaded data))
        (om/update! data :tag tagname)
        (om/update! data :view :random)))

  (defroute "/recipe/:link" [link]
    (go (<! (ensure-recipes-loaded data))
        (om/update! data :view :recipe)
        (om/update! data :permalink link)))

  (defroute "*" [*]
    (go (<! (ensure-recipes-loaded data))
        (om/update! data :view :default))))

person Michiel Borkent    schedule 29.06.2014    source источник


Ответы (1)


Использование конструкции loop без каких-либо (парковочных) операций канала внутри нее для ожидания в значительной степени полностью противоречит духу core.async. Имейте в виду, что JavaScript является однопоточным, поэтому, если вы не припаркуете поток выполнения, у вас не будет возможности запустить что-либо еще.

Имейте канал, который вы закрываете, когда операция загрузки завершена. Попробуйте читать с этого канала, когда вам нужно заблокировать; если он возвращает nil, он выполнен, а если он блокируется, то у вас есть желаемая операция ожидания. Если вы хотите периодически что-то делать во время ожидания, то читайте и с этого канала, и с тайм-аута.

person Charles Duffy    schedule 30.06.2014
comment
Спасибо, это имеет большой смысл. Я разместил свой обновленный код. Пожалуйста, прокомментируйте, если это можно улучшить. - person Michiel Borkent; 30.06.2014