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.
To be specific:
In my experience, the simplest way of handling these issues is to use core.async.
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))
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))