BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage News core.async: A Different Approach to Asynchronous Programming with Clojure and ClojureScript

core.async: A Different Approach to Asynchronous Programming with Clojure and ClojureScript

This item in japanese

Lire ce contenu en français

While it has been less than a month since the announcement of the core.async Clojure/ClojureScript library, a number of blog posts have been published describing how to use it effectively to avoid "callback hell" in front-end code, and showing off simple code resulting in some impressive in-browser demos.

core.async is a library written in Clojure to be used with both Clojure and ClojureScript. Clojure is a Lisp implementation on top of the JVM. ClojureScript is a second implementation of a large subset of Clojure that compiles to JavaScript. core.async is a good demonstration of the power of the macro facilities that a Lisp has to offer: while most other language core.async does things that would require changes to the language, in a Lisp it is possible to implement these as a library using macros.

As the name suggests, core.async is designed to make asynchronous programming simpler. It borrows a lot of ideas from Go, specifically its notions of goroutines (named go blocks in core.async) and channels. A channel is a queue with one or more publishers and one or more consumers. The mechanism is simple: producers put data onto the queue, consumers take data from the queue. Because data in Clojure/ClojureScript is immutable, channels provide a safe way to communicate between threads. Although the latter this is not a particularly interesting feature in the context of ClojureScript, because JavaScript is single-threaded.

core.async offers two ways to write to and read from channels: blocking and non-blocking. A blocking write blocks the thread until the channel has space to be written to (the buffer size of a channel is configurable), a blocking read blocks a thread until a value becomes available on the queue to be read. More interesting, and the only type supported in ClojureScript, are asynchronous channel reads and writes to channels, which are only allowed in "go blocks". Go blocks are written in a synchronous style, and internally converted to a state machine that executes them asynchronously.

Consider the following core.async-based code:

(let [ch (chan)]
  (go (while true
        (let [v (<! ch)]
          (println "Read: " v))))
  (go (>! ch "hi")
      (<! (timeout 5000))
      (>! ch "there")))

In this example, let introduces a new local variable ch, which is a new channel. Within the let's scope two go blocks are defined, the first is an eternal loop that reads (<!) a new value from channel ch into variable v. It then prints "Read: " followed by the read value to the standard out. The second go block writes (>!) two values to channel ch: "hi", it then waits 5 seconds and then writes "there" to the channel. Waiting for 5 seconds is implemented by reading from a timeout channel, which is a channel that closes itself (returns nil) after a set timeout. When running this code in the Clojure REPL (for instance), it will return instantly. It will then print "Read: hi", and 5 seconds later it will print "Read: there".

Any JavaScript programmer, looking at this while loop will immediately freak out: you cannot do blocking loops like this: the browser will freeze up for 5 seconds. The "magic" of core.async is that internally it converts the body of each go block into a state machine and turns the synchronous-looking channel reads and writes into asynchronous calls.

Here are some resources to learn more about core.async and how it can impact front-end web development:

Rate this Article

Adoption
Style

BT