Last active
March 18, 2020 19:20
-
-
Save jjsullivan5196/0904057a2ec3eb080de5c4d6f45da630 to your computer and use it in GitHub Desktop.
JavaScript async iterators in core.async
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;; 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)))) | |
Thank you very much.
I could use it successfully to translate https://github.com/SocketCluster/socketcluster/blob/master/app/server.js to ClojureScript.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.