Asynchronous ClojureScript文件预加载程序-模拟promise的函数或模式?

Asynchronous ClojureScript文件预加载程序-模拟promise的函数或模式?,asynchronous,clojure,promise,clojurescript,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)))

我正在尝试在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中没有承诺,所以我如何解决这个问题?基于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)))
(def url(atom[]))
(def加载的资源(atom[]))
(国防部资源陈(陈))
(defn加载回调[数据]
(转到(>!资源更改数据)))
;; 从主url填充url数组
(加载![]
;init ajax加载
(转到循环[]
(未加载时(=(在加载资源时计数)(在URL时计数))
(交换!已加载的资源)(

不确定哪个版本更好。

我可以想出两种方法

  • all resources
    更改为另一个atom,初始化为nil。以2x-5x/秒的速度轮询它,直到它不是nil并得到“已交付”的结果

  • 使用
    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" #<Atom@4b020acf: {:foo "bar"}>
    ;; "old-state" {}
    ;; "new-state" {:foo "bar"}
    ;; {:foo "bar"}
    
    (定义a(原子{}))
    (添加watch a:watcher)
    (fn[键原子旧态新态]
    (prn--“原子变化--”)
    (prn“键”键)
    (prn“原子”原子)
    (prn“旧州”旧州)
    (prn“新州”新州)
    (重置!a{:foo“bar})
    “原子改变了”
    “钥匙”:观察者
    “原子”#
    ;“旧州”{}
    ;“新州”{:foo“bar”}
    {:foo“bar”}
    
    假设加载资源函数返回一个通道(如cljs http/get)

    在clj中,你所需要做的就是抓住他们做一个“等等”

    (让[cs(doall(映射加载资源URL));;启动get
    其他初始化
    资源(地图)
    在cljs中,您可以在继续之前累积响应:

    (go
        (let [res (atom [])]
          (doseq [c cs]
            (swap! res conj (<! c)))
          (do-other-things @res)))
    
    (开始
    (让[res(atom[])]
    (doseq[c cs]
    
    (swap!res conj(JavaScript是一个单线程环境,因此没有阻塞等待

    如果您希望请求多个资源并在它们都已被提供时继续,我建议使用core.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)))
    

    使用
    goog.Promise
    然后将它们放入
    goog.Promise.all
    有趣的建议,因为google框架无论如何都会被导入…我还没有尝试过,但当时我坚持@alan thompson给我的想法,并使用了一个watcher,在一切完成后执行一个加载后钩子。它非常适合我的需要。它这也是一个坏主意,因为您处理的是可变状态,并且依赖于您的逻辑是无bug的。这正是
    goog.Promise。所有的
    都是为之而设计的。即使是javascript程序员也会理解这些代码。这是前端开发中非常常见的模式!也不要忘记错误处理,如果其中一个URL失败会发生什么?您是否等待永远?你的错误传播正确吗?
    保证。所有的
    都能正确处理。感谢你的想法,@Alan第一种方法与比较交付资源的数量和请求资源的数量没有什么不同,只是第三个原子存储了结果,还是我错了?我也想到了使用观察者,但他们不会t阻塞主线程。当然,我可以初始化加载,添加一个观察程序,然后完成main方法。然后观察程序将检查完整性,并在加载后调用包含主线程其余部分的函数…cljs中没有
    
    
    (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)))