ClojureScript vs clojure.core.async

I’m going to make a somewhat bold statement: core.async does not work with ClojureScript. And, in this post, I’m going to show some examples why this is true, at least for the current versions of core.async.

So let’s start by understanding a little bit about the runtime: Javascript is a single-threaded runtime that implicitly runs an event-loop. So, for example, when you ask to read a file, you can do it synchronously or asynchronously. If you decide to run in that asynchronously, it means that as soon as you issue the fs.readFile command, you need to register a callback and the control is returned to the “main thread”. It’ll keep running until it runs out of commands to execute, then the runtime will wait the result from the callback; when it returns, the function that you registered will be called with the file contents. When the function ends, the JS runtime will await to see if there’s any other pending call, and it’ll exit if there’s nothing else to do.

The same thing happens in browser environment, but in this case the callbacks are events from the DOM: like clicking on buttons or listening for changes in some elements. The same rules apply here: the runtime is single threaded and when something happens it will first execute everything that needs to be executed, then it will be called back with the event that happened.

So maybe we can change these callbacks with core.async channels right? But the answer is no, because core.asyncs go blocks will not run in different threads (because, again, the runtime is single-threaded). Instead, it creates a state machine and it’ll control of when each of these go blocks will be called, at what time, eventually replacing the event-loop that Javascript environment already have.

Why I tend to avoid core.async?

It’s no surprise that I don’t like core.async very much. For starters, it make my functional composition looks like imperative programming again. There’s also multiple issues that you need to be aware of (like, don’t use async/put! because you will have problems, deadlocks that are difficult to predict, go blocks don’t compose over functions so you loose lots of helper macros like delay).

But the most important reason is that most of the time, I’m working in ClojureScript. And it’s impossible to migrate callback to core.async.

Well, you may be tempted to write something like:

(js/someFunction "i'm async" "lol" #(async/put! some-channel %))

And one day or another you’ll have the dreadful Assert failed: No more than 1024 pending puts are allowed error. There are multiple ways around this problem, but none of then work if you can’t lose messages.