Need help with superv.async?
Click the “chat” button below for chat support from the developer who created it, or find similar developers for support.

About the developer

153 Stars 7 Forks 31 Commits 7 Opened issues


This is a Clojure(Script) library that extends core.async with error handling and includes a number of convenience functions and macros.

Services available


Need anything else?

Contributors list

superv.async CircleCI

Let it crash. The Erlang approach to build reliable systems.

Errors happen, and proper error handling cannot be bolted on top of subsystems. This library draws on the Erlang philosophy of error handling to help building robust distributed systems that deal with errors by default. A detailed description of Erlang supervisors can also be found here.

This is a Clojure(Script) library that extends core.async with error handling and includes a number of convenience functions and macros. This library is a fork of full.async. The original attempt to merge this work with it failed due to the limitations of a dynamic binding approach. The fork became reasonable, because full.async mostly deals with convenience functions, but it is not as radically focused on proper error handling. Since the error handling cannot happen through a transparent dynamic binding, some convenience is lost in

as you need to carry around the supervisor lexically. If binding support comes to ClojureScript for asynchronous contexts the projects might merge again. The binding approach also has a performance penalty though. Since the boundary of both error handling libraries is the exception mechanism and core.async channels, they still compose, but supervision will not compose with


Add this to your project. Clojars Project

Exception Handling

Exception handling is an area for which

doesn't have support out of the box. If something within a
block throws an exception, it will be logged in the thread pool or logged to the cljs console, but the block will simply return
, hiding any information about the actual exception. You could wrap the body of each
block within an exception handler but that's not very convenient.
does this internally for you and provides a set of helper functions and macros for dealing with exceptions in a simple and robust manner:
  • go-try
    : equivalent of
    but catches any exception thrown and returns via the resulting channel
  • , 
    : equivalents of
     but if the value
    is an exception, it will get rethrown. This also ensures a proper stacktrace
    between async contexts.


and the
expressions work well when you have a sequential execution of goroutines because you can just rethrow the exceptions on the higher levels of the call-stack. This fails for concurrent goroutines like go-loops or background tasks, which are often the long-term purpose of the introduction of asynchronous programming in the first place. To handle these concurrent situations we have introduced an Erlang inspired supervision concept.

Two requirements for robust systems in Erlang are:

  1. All exceptions in distributed processes are caught in a unifying supervision context
  2. All concurrently acquired resources are freed on an exception

We decided to form a supervision context in which exceptions thrown in concurrent parts get reported to a supervisor:

(require '[superv.async :refer [S go-try restarting-supervisor on-abort]])

(let [try-fn (fn [S] (go-try S (throw (ex-info "stale" {})))) start-fn (fn [rs] ;; will be called again on retries (go-try rs (on-abort rs ;; you must ensure the freeing of resources for proper restarts "do cleanup here") (try-fn rs) ;; triggers restart after stale-timeout 42))] (? S (restarting-supervisor start-fn :retries 3 :stale-timeout 1000)))

The restarting supervisor tracks all nested goroutines and waits until all are finished and have hence freed all resources (through

cleanup routines) before it tries to restart or finally returns itself either the result or the exception. This allows composition of supervised contexts. There is a default simple-supervisor
in the
namespace for convenience during development. We recommend to carry it as
along in your call stack. While
propagate errors to their supervisor, their errors, if they run concurrently, will only be caught after they become stale. For the cases of concurrent go-routines
exist. They will immediately report exceptions to the supervisor and not propagate them through the call stack, yielding an immediate restart or failure if necessary and shortening the restart cycle.

To really free resources you just use the typical stack-based try-catch-finally mechanism. To support this, channel operations

... trigger an "abort" exception when the supervisor detects an exception somewhere. This at least eventually guarantees the termination of all blocking contexts, since we cannot use preemption on errors like the Erlang VM does. In cases where you have long blocking IO without core.async, you can insert some dummy blocking operation to speed up restarts.

The supervisor tracks all thrown exceptions, so whenever they are not taken off some channel and become stale, it will timeout and restart, depending on the exception type it is tracking.

Sequences & Collections

Channels by themselves are quite similar to sequences however converting between them may sometimes be cumbersome.

provides a set of convenience methods derived from
for this:
  • <, 
    : takes all items from the channel and returns them as a collection. Must be used within a
  • <, 
    : takes all items from the channel and returns as a lazy sequence. Returns immediately.
  • , 
    takes one item from each input channel and returns them in a single collection. Results have the same ordering as input channels.
  • go-for
    is an adapted for-comprehension with channels instead of lazy-seqs. It allows complex control flow across function boundaries where a single transduction context would be not enough. For example:
(let [query-fn #(go (* % 2))
      goroutine #(go [%1 %2])]
     (<? S (go-for S [a [1 2 3]
                        :let [b ( S (query-fn a))]
                        :when (> b 5)]
                      ( S (goroutine a b)))))

Note that channel operations are side-effects, so this is best used to realize read-only operations like queries. Nested exceptions are propagated according to go-try semantics.


you needed to form some boilerplate around function boundaries like this, which is much less succinct:
(<? (->> [1 2 3]
           (map #(go [% ( (query-fn %))]))
           (async/into [])
           (filter #(> (second %) 5))
           (map (apply goroutine))

Parallel Processing

lets you apply a function to channel's output in parallel, returning a new channel with results.


For readability of code,

follows these conventions from
: * Async operators that throw exceptions use
in place of
, for example throwing counterpart of
* Functions that return channel that will contain zero or one value (typically
result of 
blocks) are sufixed with
. Similarly operators that expect zero/single value channel as input are prefixed with
(for example
* Functions that return channel that will contain zero to many values are
sufixed with 
. Similarly operators that expect zero to many value channel as input are prefixed with
(for example

Change Log



  • add spec signatures


Copyright (C) 2015-2016 Christian Weilbach, 2016 FullContact. Distributed under the Eclipse Public License, the same as Clojure.

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.