没有内置的功能可以像它一样工作
for await (let thing of asyncIterable) { ... }
而且,据我所知,没有库实现了类似的语法便利性。
但是,据我的理解,AsyncIterable 协议是有一个以 Symbol.asyncIterable
为键的方法。这个方法返回一个 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 能否查找符号)