考虑这样的数据集:
(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-a
和parse-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))))
答案 0 :(得分:3)
我会说promise-chan
在这里弊大于利。问题是大多数core.async
API(a/merge
,a/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
)并解决此问题,您需要来你自己的东西(重新发明轮子)。因此,如果您需要承诺,我建议您使用特定的库,例如manifold
或promesa
。
答案 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)))