Skip to content

Instantly share code, notes, and snippets.

@jjsullivan5196
Last active March 18, 2020 19:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jjsullivan5196/0904057a2ec3eb080de5c4d6f45da630 to your computer and use it in GitHub Desktop.
Save jjsullivan5196/0904057a2ec3eb080de5c4d6f45da630 to your computer and use it in GitHub Desktop.
JavaScript async iterators in core.async
(ns async-iterators
"Functions and procedures for using JavaScript `AsyncIterable`s with
`core.async`. Find out more about async iteration at
https://github.com/tc39/proposal-async-iteration and
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of
Kudos to @paul931224 on clojurians for trying this out w/me."
(:require [cljs.core.async :as async :refer [go >! close!]]))
(defn get-iterator
"Create an async iterator object from `iterable`. `iterable` must have a method
definition for `js/Symbol.asyncIterator`."
[iterable]
(js-invoke iterable js/Symbol.asyncIterator))
(defn push-next
"Run `step` when `value` is taken from `output` channel."
[output value step]
(go (when (>! output value)
(step))))
(defn take-value
"Pull the value from an async iterator `element`."
[element]
(when (not (.-done element))
(.-value element)))
(defn onto-chan
"Iteratively take values from async `iterator` and push them to channel
`output`. By default, will close `output` when `iterator` is
exhausted. Provide false `close?` argument to leave the output open."
([output iterator] (onto-chan output iterator true))
([output iterator close?]
(let [step #(onto-chan output iterator)]
(.. iterator
(next)
(then
(fn [element]
(if-let [value (take-value element)]
(push-next output value step)
(when close? (close! output))))
(fn [err]
(push-next output err step)))))))
(defn to-chan
"Create an unbuffered `core.async` channel that pulls values from async
`iterator`. Channel will close when `iterator` is exhausted."
[iterator]
(let [iter-chan (async/chan)]
(onto-chan iter-chan iterator)
iter-chan))
;; I'll throw in more here once I have actual testcases, but this is roughly how things should work
(require '[async-iterators :as async-iter]
'[cljs.core.async :as async :refer [go-loop <!]])
(declare make-async-iterable)
;; Note: need to get iterator object first, unlike `for...await of` in JavaScript
(let [my-async-iterable (make-async-iterable)
my-async-iterator (async-iter/get-iterator my-async-iterable)
iter-chan (async-iter/to-chan my-async-iterator)]
(go-loop []
(println (<! iter-chan))))
@jjsullivan5196
Copy link
Author

jjsullivan5196 commented Mar 18, 2020

These have pretty much 1-to-1 behavior with onto-chan and to-chan from core.async, so you can refer to those for examples.
https://clojuredocs.org/clojure.core.async/onto-chan
https://clojuredocs.org/clojure.core.async/to-chan

Small note: this module 100% delegates error handling to consumers. Errors will be passed down as values, so make sure to catch them appropriately.

@p4ulcristian
Copy link

Thank you very much.
I could use it successfully to translate https://github.com/SocketCluster/socketcluster/blob/master/app/server.js to ClojureScript.

https://github.com/paul931224/socketcluster-cljs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment