Introduction

A lightweight promise/future abstraction built on top of JDK8 CompletableFuture.

Project Maturity

Since promissum is a young project there may be some API breakage.

Install

The simplest way to use promissum library in a Clojure project is by including it as a dependency:

[funcool/promissum "0.3.3"]

User Guide

Introduction

The promise consists in a container that eventually will contain a value with builtin support for error handling. So the promise has three different states:

  • resolved: means that the promise contains a value.

  • rejected: means thet the promise contains an error.

In summary: is the abstraction that represents the result of an asynchronous operation that will be eventually available.

The promissum's promise abstraction just works on top of the awesome CompletableFuture future/promise implementation available in JDK8. And offers a very lightweight layer on top of it.

Note
Clojure comes with a builtin promise abstraction but it is designed only for blocking operations, and in async environments the blocking operations are completely discouraged.

Creating a promise

It there several different ways to create a promise in promissum library. You can create it already resolved with initial value or already rejected with an exception.

Let start with a basic example using the commonly known promise delivering in clojure:

(require '[promissum.core :as p])

(def pr (p/promise))

(future
  (Thread/sleep 200)
  (p/deliver pr 20))]

(p/then pr (fn [v]
             (println v)))

;; After 200ms it will print `20`

An other way to create a promise is using a factory function that can resolve or reject promise in asynchronous way. If you are familiar with javascript promises, you will found that very familiar:

(def pr (p/promise
          (fn [deliver]
            (deliver 1))))

promissum also exposes a clojure’s future alternative that works in the same way with the difference that it returns a CompletableFuture:

(def pr (p/future
          (Thread/sleep 200)
          2))
@pr
;; => 2

You should know that promise and future functions just return a CompletableFuture instance without additional wrapping.

Creating a future

The promissum library also exposes a convenient macro for clojure future macro replacement. It works in exactly manner that the clojure version, with a little difference that it returns a composable promise that can be easily chained in an asynchronous way.

See an example:

(deref (p/future
         (Thread/sleep 200)
         (+ 1 2)))
;; => 3

Blocking operations

The promissum's promises can be used as drop in replacement for clojure promises, because them offers also blocking operations:

@pr
;; => 1

If you try to deref a promise that is rejected, the exception will be rereaised in the calling thread. You should take care that the reraised exception is wrapped in ExecutionException in the same way as builtin clojure promise/future does.

For avouid unnecesary pain constantly handling that, promissum exposes the await function. It has the same call signature as clojure builtin deref function but if promise contains a exception that exception will be reraised as is (without additional wrapping).

(p/await pr)
;; => 1

State checking

promissum provides useful predicates that will allow check the state of a promise in any time.

Let see some examples:

(def pr (p/promise 2))

(p/promise? pr)
;; => true

(p/pending? pr)
;; => false

(p/resolved? pr)
;; => true

(p/rejected? pr)
;; => false

(p/done? pr)
;; => true

The done? predicate checks if a promise is fullfiled, independently if is resolved or rejected.

Promise chaining

It there different ways to compose/chain computations using promises. We will start with the basic: lineal way of chaining computations.

That can be done using then or chain functions exposed in promissum.core namespace. Bot them are mainly interchangeable. The main differencia is that chain is variadic and then not.

(def pr (-> (p/promise 2)
            (p/then inc)
            (p/then inc)))

(p/await pr)
;; => 4

And here the same example using the chain function instead of then:

(def pr (p/chain (p/promise 2) inc inc))
(p/await pr)
;; => 4

Later, thanks to the cats library, it there other few methods of create promise compositions in more powerfull way: mlet and alet macros.

For demostration purposes, imagine that you have this function that emulates async operation and return a promise:

(require '[cats.core :as m])
(require '[promissum.core :as p])

(defn sleep-promise
  [wait]
  (p/promise (fn [deliver]
               (Thread/sleep wait)
               (deliver wait))))

Now, we will try to use this function together with mlet macro and additionally messure the execution time:

(time
 (p/await (m/mlet [x (sleep-promise 42)
                   y (sleep-promise 41)]
             (m/return (+ x y)))))
;; "Elapsed time: 84.328182 msecs"
;; => 83

The mlet bindings are executed sequentially, waiting in each step for promise resolution. If an error occurs in some step, the entire composition will be short-circuited, returing exceptionally resolved promise.

The main disadvantage of mlet is that it’s evaluation model is strictly secuential. It is ok for some use cases, when the sequential order is mandatory. But, if the strictly secuential model is not mandatory, mlet does not take the advantage of concurrency.

For solve this problem, it there alet macro. It is almost identical to mlet from the user experience, but internally it is based in very different abstractions.

Now, we will try to do the same example but using the alet macro:

(time
 @(m/alet [x (sleep-promise 42)
           y (sleep-promise 41)]
    (+ x y)))
;; "Elapsed time: 44.246427 msecs"
;; => 83

We can observe that the return value is identical to the previous example, but it takes almost half of time to finish execute all the computations. This is happens because alet is more smarter macro and calculates de dependencies between declared bindings and executes them in batches; taking fully advantage of having fully miltithreaded/concurrent environment as is JVM.

You can read more about that here.

Error handling

One of the advantages of using promise abstraction is that it natively has a notion of error, so you don’t need reinvent it. If some of the computations of the composed promise chain/pipeline raises an exception, that one is automatically propagated to the last promise making the effect of short-circuiting.

Let see an example:

(def pr (p/chain (p/promise 2)
                 (fn [v] (throw (ex-info "test" {})))))
(p/await pr)
;; => clojure.lang.ExceptionInfo "test" ...

For exception catching facilities, promissum exposes a catch function. It just works like then but with exceptions. It attaches a next computation that only will be executend if a previous computation resolves exceptionally:

(def pr (-> (p/promise 2)
            (p/then (fn [v] (throw (ex-info "foobar" {}))))
            (p/catch (fn [error] :nothing))))

(p/await pr)
;; => :nothing

The catch chain function also return a promise, that will be resolved or rejected depending on that will happen inside the catch handler.

Working with collections

In some circumstances you will want wait a completion of few promises at same time, and promissum also provides helpers for that.

Imagine that you have a collection of promises and you want to wait until all of them are resolved. This can be done using the all combinator:

(def pr (p/all [(p/promise 1)
                (p/promise 2)]))
(p/await pr)
;; => [1 2]

It there are also circumstances where you only want arbitrary select of the first resolved promise. For this case, you can use the any combinator:

(def pr (p/any [(p/promise 1)
                (p/promise (ex-info "error" {}))]))
(p/await pr)
;; => 1

Later, for more advanced use cases, promissum is an algebraic structure that implements the associative binary operation usually called mappend:

(require '[cats.core :as m])

(def pr (m/mappend (p/promise {:a 1})
                   (p/promise {:b 2})))
(p/await pr)
;; => {:a 1 :b 2}

If you are interested in knowing more about it, plase refer to the cats documentation.

Developers Guide

Contribute

Unlike Clojure and other Clojure contrib libs, does not have many restrictions for contributions. Just open a issue or pull request.

Get the Code

promissum is open source and can be found on github.

You can clone the public repository with this command:

git clone https://github.com/funcool/promissum

Run tests

For run tests just execute this:

lein test

License

promissum is licensed under BSD (2-Clause) license:

Copyright (c) 2015 Andrey Antukh <niwi@niwi.nz>

All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.