ClojureScript文件预加载器 - 模拟承诺的函数或模式?

时间:2016-11-10 06:00:55

标签: asynchronous clojure promise clojurescript

我试图在ClojureScript中创建文件预加载器。我的想法是这样的模式:

(def urls (atom[]))
(def loaded-resources (atom []))
(def all-resources (promise))

(defn loading-callback []
  (if (= (count urls) (count loaded-resources))
    (deliver all-resources loaded-resources)))

;; fill urls array
;; start ajax-loading with loading-callback on success

所以我的主要功能可以继续,直到它需要资源然后等待它们,这在Clojure中运行良好。

不幸的是,承诺并不存在于ClojureScript中,那么我该如何解决这个问题呢?有promesa基于core.async频道为CLJS带来了承诺,但它只允许等待单个函数执行的未来类似的承诺,这些承诺不足以满足我的需求(至少在昨天我一直在考虑的方式......)。

有任何解决此问题的建议吗?也许使用完全不同的模式?我想让代码尽可能简单,以说服我团队中的人试用CLJ / S.

修改

在艾伦的第二个想法之后:

(def urls (atom[]))
(def loaded-resources (atom []))

(defn loading-callback [data]
  (swap! loaded-resources conj data))

(defn load! [post-loading-fn]
  (add-watch loaded-resources :watch-loading
    (fn [_ _ _ cur]
      (if (= (count cur) (count @urls)) (post-loading-fn))))
  ;; init ajax loading
  )

(defn init []
;; fill urls array
  (load! main))

(main []
  (do-terrific-stuff @loaded-resources))

与此同时,我曾尝试使用core.async

(def urls (atom []))
(def loaded-resources (atom []))
(def resource-chan (chan))

(defn loading-callback [data]
  (go (>! resource-chan data)))

;; fill url array from main

(load! []
  ;; init ajax loading
  (go-loop []
    (when-not (= (count @loaded-resources) (count @urls))
      (swap! loaded-resources conj (<! resource-chan))
      (recur)))

不确定哪个版本更好。

3 个答案:

答案 0 :(得分:1)

我可以想到两种方法。

  1. all-resources更改为另一个原子,初始化为零。轮询2x-5x / sec,直到它不是零并且具有“交付”结果。

  2. 使用add-watch注册一个回调函数,以便在更改值时执行。这取代了阻塞,直到交付价值。这里描述:http://clojuredocs.org/clojure.core/add-watch

  3. 他们展示了一个很好的例子:

    (def a (atom {}))
    
    (add-watch a :watcher
      (fn [key atom old-state new-state]
        (prn "-- Atom Changed --")
        (prn "key" key)
        (prn "atom" atom)
        (prn "old-state" old-state)
        (prn "new-state" new-state)))
    
    (reset! a {:foo "bar"})
    
    ;; "-- Atom Changed --"
    ;; "key" :watcher
    ;; "atom" #<Atom@4b020acf: {:foo "bar"}>
    ;; "old-state" {}
    ;; "new-state" {:foo "bar"}
    ;; {:foo "bar"}
    

答案 1 :(得分:0)

假设您的加载资源函数返回一个通道(如cljs-http / get)。

在clj中,您需要做的就是坚持让他们做一个&#34;等待所有&#34;。

(let [cs  (doall (map load-resource urls))   ;; initiate the get
      ...                                    ;; other initialisation
      res (map <!! cs)]                      ;; wait-all for the resources
  (do-other-things res))

在cljs中,您可以在继续之前累积回复:

(go
    (let [res (atom [])]
      (doseq [c cs]
        (swap! res conj (<! c)))
      (do-other-things @res)))

答案 2 :(得分:0)

JavaScript是单线程环境,因此没有阻塞等待。

如果您希望请求多个资源并继续,如果它们都已提供,我建议您使用core.async,尤其是pipeline-async。它有一个旋钮来微调异步请求的并行性。以下是用于实现您想要的惯用ClojureScript代码:

(ns example.core
  (:require [cljs.core.async :refer [chan take! put! pipeline-async] 
                             :as async]))

(defn load-resources [urls on-resources]
  (let [urls-ch (chan (count urls))
        resources-ch (chan)]
    ;; Create pipeline:
    (pipeline-async 10 ;; have at most 10 requests in flight at
                       ;; the same time, finetune as desired
                    resources-ch 
                    (fn [url done-ch]
                      ;; Pseudo code:
                      (request-resource 
                       url
                       (fn [loaded-resource]
                         (put! done-ch loaded-resource))))
                    urls-ch)
    ;; Eagerly aggregate result until results-ch closes, then call back:
    (take! (async/into [] resources-ch) on-resources)
    ;; Start the party by putting all urls onto urls-ch 
    ;; and then close it:
    (async/onto-chan urls-ch urls)))