2016-11-10 1 views
1

Ich versuche, einen Dateipreloader innerhalb von ClojureScript zu erstellen. Meine Idee war, ein Muster wie folgt aus:ClojureScript Datei Preloader - Funktion oder Muster, um Versprechen zu emulieren?

(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 

So konnte meine Hauptfunktion weitergehen, bis sie die Ressourcen erfordern würde, und dann auf sie warten, die gut in Clojure funktioniert.

Leider gibt es keine Versprechungen in ClojureScript, also wie kann ich dieses Problem umgehen? Es gibt promesa Versprechen zu CLJS basierend auf core.async-Kanälen, aber es erlaubt nur zukunftsorientierte Versprechen, die auf die Ausführung einer einzelnen Funktion warten, die meinen Bedürfnissen nicht genügt (zumindest in der Art, wie ich gestern darüber nachgedacht habe) ...).

Irgendwelche Vorschläge, um dieses Problem zu lösen? Vielleicht ein ganz anderes Muster verwenden? Ich möchte den Code so einfach wie möglich halten, um die Leute in meinem Team davon zu überzeugen, CLJ/S auszuprobieren.

EDIT:

Nach Alans zweite Idee:

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

Inzwischen hatte ich versucht, 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))) 

Nicht sicher besser, welche Version zu verwenden ist.

+1

Verwenden 'goog.Promise' und schieben sie dann in' goog.Promise.all' – ClojureMostly

+0

Interessanter Vorschlag, da das Google-Framework sowieso importiert wird ... Ich habe es noch nicht versucht, aber im Moment habe ich mich an die Idee gehalten, @alan-thompson gab mir einen Beobachter, der eine Post- Ladehaken, wenn alles fertig ist. Es funktioniert ganz gut für meine Bedürfnisse. – waechtertroll

+1

Es ist auch eine schlechte Idee, da Sie sich mit veränderlichen Zustand beschäftigen und sich darauf verlassen, dass Ihre Logik fehlerfrei ist. Genau dafür wurde "goog.Promise.all" entwickelt. Selbst JavaScript-Programmierer würden diesen Code verstehen. Es ist ein sehr häufiges Muster in der Frontend-Entwicklung! – ClojureMostly

Antwort

1

Ich kann mir zwei Ansätze vorstellen.

  1. Ändern Sie all-resources zu einem anderen Atom, initialisiert auf Null. Poll es 2x-5x/Sek bis es nicht Null ist und das "geliefert" -Ergebnis hat.

  2. Verwenden Sie add-watch, um eine Rückruffunktion zu registrieren, die ausgeführt wird, wenn der Wert geändert wird. Dies ersetzt die Blockierung, bis der Wert geliefert wird. Es ist hier beschrieben: http://clojuredocs.org/clojure.core/add-watch

Sie ein gutes Beispiel zeigen:

(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

Danke für deine Gedanken, @Alan Der erste Ansatz unterscheidet sich nicht von dem Vergleich der Anzahl der gelieferten Ressourcen mit der Anzahl der angeforderten, außer dass ein drittes Atom das Ergebnis speichert, oder liege ich falsch? Ich habe auch Beobachter beobachtet, aber sie werden den Hauptthread nicht blockieren. Ich könnte natürlich das Laden initialisieren, einen Beobachter hinzufügen und dann die Hauptmethode beenden. Der Watcher überprüft dann die Vollständigkeit und ruft nach dem Laden eine Funktion auf, die den Rest des Hauptthreads enthält ... – waechtertroll

0

Ihre Last-Ressource-Funktion gibt einen Kanal Unter der Annahme (wie cljs-http/get).

In clj, alles, was Sie tun müssen, ist an ihnen zu halten, um eine "Warte-alles" zu tun.

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

In cljs, können Sie die Antworten akkumulieren, bevor Sie fortfahren:

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

Es gibt kein '

+0

Danke für das Aufzeigen. Antwort aktualisiert – rmcv

0

JavaScript ist eine Single-Threaded-Umgebung, so dass es keine Sperrwartezeit ist.

Wenn Sie mehrere Ressourcen anfordern und fortfahren möchten, wenn sie alle geliefert wurden, empfehle ich die Verwendung von core.async und speziell pipeline-async. Es verfügt über einen Drehknopf, um die Parallelität von asynchronen Anforderungen zu optimieren. Hier ist idiomatische ClojureScript Code zu erreichen, was Sie wollen:

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