Core.async:从promise-chans

时间:2016-09-17 23:08:44

标签: clojure core.async

考虑这样的数据集:

(def data [{:url "http://www.url1.com" :type :a}
           {:url "http://www.url2.com" :type :a}
           {:url "http://www.url3.com" :type :a}
           {:url "http://www.url4.com" :type :b}])

应该并行请求这些URL的内容。根据项目的:类型值,这些内容应由相应的函数解析。解析函数返回集合,一旦所有响应都到达,它们应该连接在一起。

因此,假设有函数parse-aparse-b,它们在传递包含HTML内容的字符串时都返回字符串集合。

看起来core.async可能是一个很好的工具。人们可以为每个项目单个通道设置单独的通道。我不确定哪种方式更适合这里。通过多个通道,可以使用换能器进行后处理/解析。还有一个特殊的promise-chan可能适合这里。

这是一个代码草图,我正在使用基于回调的HTTP kit函数。不幸的是,我在go块中找不到通用解决方案。

(defn f [data] 
  (let [chans (map (fn [{:keys [url type]}] 
                     (let [c (promise-chan (map ({:a parse-a :b parse-b} type)))] 
                       (http/get url {} #(put! c %))
                       c))
                   data)
        result-c (promise-chan)] 
    (go (put! result-c (concat (<! (nth chans 0))
                               (<! (nth chans 1))
                               (<! (nth chans 2))
                               (<! (nth chans 3)))))
    result-c))

结果可以这样读:

(go (prn (<! (f data))))

4 个答案:

答案 0 :(得分:3)

我会说promise-chan在这里弊大于利。问题是大多数core.async API(a/mergea/reduce等)依赖于渠道将在某个时刻关闭的事实,promise-chan s依次不会关闭。< / p>

因此,如果坚持使用core.async对您来说至关重要,那么更好的解决方案就是不使用promise-chan,而是使用普通频道,这将在首先put!之后关闭:< / p>

...
(let [c (chan 1 (map ({:a parse-a :b parse-b} type)))]
  (http/get url {} #(do (put! c %) (close! c)))
  c)
...

此时,您正在使用封闭渠道,事情变得更简单了。要收集所有值,您可以执行以下操作:

;; (go (put! result-c (concat (<! (nth chans 0))
;;                            (<! (nth chans 1))
;;                            (<! (nth chans 2))
;;                            (<! (nth chans 3)))))
;; instead of above, now you can do this:
(->> chans
     async/merge
     (async/reduce into []))

UPD (以下是我的个人意见):

似乎,使用core.async渠道作为承诺(以promise-chan的形式或在单put!之后关闭的渠道)并非最佳方法。当事情发展的时候,事实证明core.async API整体上(你可能已经注意到了)并不是那么令人愉快。还有几个unsupported constructs,这可能会迫使你写出比它更少的惯用代码。此外,没有内置错误处理(如果go内发生错误 - 阻止,go - 阻止将静默返回nil)并解决此问题,您需要来你自己的东西(重新发明轮子)。因此,如果您需要承诺,我建议您使用特定的库,例如manifoldpromesa

答案 1 :(得分:0)

使用async.core中的pipeline-async同时启动http/get异步操作,同时按照与输入相同的顺序提供结果:

(let [result (chan)] 
  (pipeline-async 
    20 result
    (fn [{:keys [url type]} ch]
      (let [parse ({:a parse-a :b parse-b} type)
            callback #(put! ch (parse %)(partial close! ch))]  
        (http/get url {} callback)))
    (to-chan data))
  result)

答案 2 :(得分:0)

我也想要此功能,因为我真的很喜欢core.async,但我也想在传统的JavaScript承诺之类的某些地方使用它。我想出了使用宏的解决方案。在下面的代码中,<?<!是同一件事,但是如果有错误,它将抛出该错误。它的行为类似于Promise.all(),如果它们全部成功,则从通道返回所有返回值的vector。否则它将返回第一个错误(因为<?将导致它抛出该值)。

(defmacro <<? [chans]
  `(let [res# (atom [])]
     (doseq [c# ~chans]
       (swap! res# conj (serverless.core.async/<? c#)))
     @res#))

如果您想查看该函数的完整上下文,请访问GitHub。它受到了大卫·诺伦(David Nolen)的blog post的极大启发。

答案 3 :(得分:0)

如果仍然有人在看这个,请添加@OlegTheCat的答案:

您可以使用单独的渠道处理错误。

(:require [cljs.core.async :as async]
            [cljs-http.client :as http])
(:require-macros [cljs.core.async.macros :refer [go]])

(go (as-> [(http/post <url1> <params1>)
           (http/post <url2> <params2>)
           ...]
          chans
          (async/merge chans (count chans))
          (async/reduce conj [] chans)
          (async/<! chans)
          (<callback> chans)))