Welcome! Please see the About page for a little more info on how this works.

+1 vote
in ClojureScript by

Does clojurescript has any way to work with AsyncIterable inteface in JS. The JS way is to use for await of .
For normal async/await or promise, I found - https://clojurescript.org/guides/promise-interop

I'm trying to build a IPFS file browser and its JS api returns AsyncIterable.

1 Answer

0 votes
by
edited by
 
Best answer

There is nothing built in that works in the same way as

for await (let thing of asyncIterable) { ... }

And, as far as I know, there isn't any library which implements a similar syntactic convenience.

But, to my understanding, the protocol for an AsyncIterable is that there is a method keyed under Symbol.asyncIterable. This method returns an AsyncIterator, which is object with a next method that when called returns a Promise<{ done: false, value: T } | { done: true }>.

This is a similar situation to the iterator protocol, where code that looks like this

for (let o of iterable) { ... }

effectively becomes

let iterator = iterable[Symbol.iterator]();
while (true) {
  let next = iterator.next();
  if (next.done) {
    break;
  }
  else {
    let o = next.value;
    ...
  }
}

instead code that looks like this

for await (let o of asyncIterable) { ... }

effectively becomes this

let asyncIterator = asyncIterable[Symbol.asyncIterable]();
while (true) {
  let next = await iterator.next();
  if (next.done) {
    break;
  }
  else {
    let o = next.value;
    ...
  }
}

and then the await can be thought of as being transformed by the runtime into promise chaining.

If we want to have a similar construct in CLJS, we need to mimic these basic semantics.

This is my attempt at making a doseq for async iterables. I haven't tested it well and I'm sure other people will have better ideas, but its a start.

(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#)))))))))

and you should be able to use it to build up other abstractions, like an async version of 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))

(credit to MaurĂ­cio Szabo for the js* part of the code - I had no clue aget couldn't look up symbols)

by
I think we should have a helper to convert AsyncIterable into a chan that will deliver multiple values. The syntactical stuff we're not going to do. Tracking here -https://clojure.atlassian.net/browse/ASYNC-239
...