I'm writing an Electron application, and in this application I need to interact with some of the Node.js APIs - read files, get directory entries, listen to events.
Of course, I can write ClojureScript same way I would write JavaScript, but I want to know what is ClojureScripts take on callback-style APIs, streams, EventEmitters and how do I write wrappers around node.js APIs in a way that does not to look alien in ClojureScript.
To be specific:
fs.readdir
)In my experience, the simplest way of handling these issues is to use core.async.
Callback Style
For example, reading a directory:
(def fs (js/require "fs"))
(defn read-dir [path]
(let [out (async/chan)]
(.readdir fs path
(fn [err files]
(async/put! (if err err files))
(async/close! out)))
out))
I pass the result in to a channel, even if that result is an error. This way, the caller can handle the error by doing. E.g.:
(let [res (<! (read-dir "."))]
(if (instance? js/Error res)
(throw res)
(do-something res))
In my own projects I use cljs-asynchronize, which allows you to convert NodeJS callback style functions in to core.async compatible functions. For example, this is the same as the first example:
(defn read-dir [path]
(asynchronize
(.readdir fs path ...)))
Lastly, for a nicer way of handling errors through channels, I personally found "Error Handling with Clojure Async" quite useful. So, you can write the error handling code above like:
(try
(let [res (<? (read-dir "."))]
(do-something res))
(catch js/Error e
(handle-error e))
Streams
Stream API's are even simpler:
(defn create-read-stream [path]
(let [out (async/chan)
stream (.createReadStream fs path)]
(.on stream "close" #(async/close! out))
(.on stream "data" #(async/put! out %))
out))
I think the answer to your question is "It depends.".
Always consider all options. Just because core.async
makes sense in one setting does not make it the best option everywhere.
It is fine to use a normal callback if that is called once only and requires no coordination with anything else (eg. fs.readdir
).
core.async
is great for stream like things where you get a certain chain of events that you want to accumulate into a final result (eg. "data","data","data","close","end"
).
Promises are another option as well.
The more coordination you need the more sense core.async
makes. If you don't need any consider the alternatives.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With