2016-11-10 82 views
1

我想在ClojureScript中創建一個文件預加載器。我的想法是這樣的模式:ClojureScript文件預加載器 - 函數或模式來模擬promise?

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

不知道哪個版本更好。

+1

使用'goog.Promise',然後將它們推到'goog。Promise.all' – ClojureMostly

+0

有趣的建議,因爲谷歌框架被導入反正...我還沒有嘗試過,但此刻我堅持@ alan-thompson的想法給了我,並使用了一個觀察者,當一切完成時加載掛鉤。它適合我的需求。 – waechtertroll

+1

這也是一個壞主意,因爲你處理可變狀態,並依靠你的邏輯無缺陷。這正是「goog.Promise.all」所做的。即使JavaScript編碼器會理解該代碼。這是前端開發中非常普遍的模式! – ClojureMostly

回答

1

我能想到2種方法。

  1. all-resources更改爲另一個原子,初始化爲零。調查它的2x-5x /秒,直到它不是零,並有「交付」的結果。

  2. 使用add-watch註冊一個回調函數以在值更改時執行。這取代了阻塞,直到交付價值。在此描述:http://clojuredocs.org/clojure.core/add-watch

他們展示一個很好的例子:

(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" #<[email protected]: {:foo "bar"}> 
;; "old-state" {} 
;; "new-state" {:foo "bar"} 
;; {:foo "bar"} 
+0

感謝您的想法,@Alan第一種方法與傳遞的資源數量與請求數量的比較沒有什麼不同,只是第三個原子存儲結果,或者我錯了嗎?使用觀察者也出現在我身上,但他們不會阻止主線程。當然,我可以初始化加載,添加觀察者,然後完成主要方法。然後觀察者會檢查完整性並在加載後調用包含主線程其餘部分的函數... – waechtertroll

0

假設你的負載資源函數返回一個通道(如cljs-HTTP/GET)。

在clj中,你需要做的就是堅持讓他們做一個「等待所有」。

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

cljs中沒有'

+0

感謝您指出。答案已更新。 – rmcv

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