Yury Solovyov Yury Solovyov - 11 months ago 81
Node.js Question

Idiomatic conversion of node.js APIs to ClojureScript

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:

  1. How do write an API that wraps callback-style node.js API. (say,

  2. How do I interact with EventEmitter-like APIs?

  3. (Probably close to p.2) How do I work with node.js streams API?

Answer Source

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

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]
    (.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:

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