没有内置的可以直接工作的方式
for await (let thing of asyncIterable) { ... }
据我所知,也没有任何库实现了类似的语法便捷性
但是,据我理解,AsyncIterable 的协议是在 Symbol.asyncIterable
键下存在一个方法。这个方法返回一个具有 next
方法的 AsyncIterator
对象,当调用 next
方法时,它返回一个 Promise<{ done: false, value: T } | { done: true }>
。
这与迭代器协议类似,其中的代码看起来像这样
for (let o of iterable) { ... }
实际上变成了这样的代码
let iterator = iterable[Symbol.iterator]();
while (true) {
let next = iterator.next();
if (next.done) {
break;
}
else {
let o = next.value;
...
}
}
而是看起来像这样的代码
for await (let o of asyncIterable) { ... }
实际上变为这个
let asyncIterator = asyncIterable[Symbol.asyncIterable]();
while (true) {
let next = await iterator.next();
if (next.done) {
break;
}
else {
let o = next.value;
...
}
}
然后我们可以将 await 视为被运行时转换成了 Promise 链式调用。
如果我们想在 CLJS 中实现类似的构造,就需要模仿这些基本语义。
这是我尝试为异步迭代器创建一个 doseq
的例子。我没有充分测试它,相信其他人会有更好的想法,但这算是一个开始。
(defmacro async-doseq [[binding async-iterable] & body]
`(let [async-iterator# ((js* "~{}[Symbol.asyncIterator]" ~async-iterable))]
(fn handle-next# []
(let [next# (.next async-iterator#)]
(.then
next#
(fn [res#]
(if (.- res# done)
nil
(do (let [~binding (.- res# value)]
~@body)
(handle-next#)))))))))
你可以用它来构建其他抽象,比如异步版本的 for
(defmacro async-for [[binding async-iterable] & body]
`(let [results# (transient [])]
(-> (async-doseq [~binding ~async-iterable]
(conj! results# (do ~@body)))
(.then (fn [_] (persistent! results#))))))
;; If this yields Promise<"a">, Promise<"b">, Promise<"c">
;; this expression should yield Promise<["A" "B" "C"]>
(async-for [node-name (ipfs/node-ids ...)]
(string/upper-case node-name))
(感谢 Maurício Szabo 对代码中 js* 部分的贡献 - 我不知道 aget 不能查找到符号)