A promise library for Clojure and ClojureScript.
On the JVM paltform promesa is built on top of completable futures (requires jdk8). On JS engines it is built on top of the bluebird promise library.
Since promesa is a young project there may be some API breakage.
Just include the following lines in your dependency vector on project.clj:
This package requires JDK8 if you are using it on the JVM and all environments that the bluebird library supports on JS engines.
A promise is an abstraction that represents the result of an asynchronous operation that has the notion of error.
This is a list of all possible states for a promise:
resolved: means that the promise contains a value.
rejected: means that the promise contains an error.
pending: means that the promise does not have value.
The promise can be considered done when it is resolved or rejected.
There are several different ways to create a promise instance:
(require '[promesa.core :as p]) ;; Create a fulfilled promise (p/promise 1) ;; => #<Promise >
promise function receives a plain value, it returns a resolved promise with
the provided plain value. If it receives an instance of
Error, it returns a
Also, it accepts a factory callback that receives two callable parameters:
reject. So you can use one or other to resolve or reject the promise
(p/promise (fn [resolve reject] (resolve 1)))
An other way to create a promise is using the
do* macro which works similar to the
factory callback with the exception that to resolve the promise instead of calling
resolve callback we just need to return the value:
(p/do* (let [a (rand-int 10) b (rand-int 10)] (+ a b)))
do* blocks work similarly to clojure’s
do block, so you can put any
expression but only the last one will be returned and that expression can be a
plain value or an other promise.
If an exception is raised inside the
do* block, it will return the rejected
promise instead of re-raising the exception on the stack.
In both platforms the promise factory function is executed synchronously and making it blocking or not blocking is the user’s responsibility.
The most common way to apply a function to a promise (or in other words, to chain
its execution) is using the well known
(def result (->> (p/promise 1) (p/map inc))) @result ; only on the jvm ;; => 2
For people coming from the JS world, there is also the
then function that works
in the same way as
map but with the parameters inverted:
(def result (-> (p/promise 1) (p/then inc))) @result ; only on the jvm ;; => 2
If you want to apply multiple functions instead of using multiple
map`s, you can use the `chain function:
(def result (-> (p/promise 1) (p/chain inc inc inc)) @result ; only on the jvm ;; => 4
There is also the
mapcat function that removes one level of nesting when dealing
with multiple promises. It is specially useful if the function that you want to
apply to also returns a promise instead of a value:
(def incp #(p/resolved (inc %))) (def result (->> (p/promise 1) (p/mapcat incp))) @result ; only on the jvm ;; => 2
mapcat function is only useful in the JVM platform. In JS engines, the
function already flattens the result magically (because the underlying implementation
The promesa library comes with convenient syntax-sugar that allows you to create a
compositions that looks like synchronous code while using the clojure’s familiar
(require '[promesa.core :as p]) (defn sleep-promise [wait] (p/promise (fn [resolve reject] (p/schedule wait #(resolve wait))))) (def result (p/alet [x (p/await (sleep-promise 42)) y (p/await (sleep-promise 41)) z 2] (+ x y z))) @result ; only on the jvm ;; => 85
alet macro behaves identical to the
let with the exception that it always
return a promise and allows you to mark async operations with the
placeholder making it looks like a synchronous operation.
If an error occurs at any step the entire composition will be short-circuited, returning exceptionally resolved promise.
In contrast to the
alet macro, the
async macro is more general purpose
and enables the usage of
await in any place (not only on the let
bindings). Let see an example:
(def p (async (dotimes [i 3] (p/await (p/delay 100)) (println "i=" i)) 10)) @p ;; i=0 ;; i=1 ;; i=2 ;; => 10
As expected, it returns a promise which will be resolved with result of the body when completed.
If you are familiar with core.async
async macro works
in the same way (in fact, it uses core.async machinery to archive
Because of some differences in the macro implementation in clj and cljs, the
clojure version of macro is available in
If you are not familiar with
await syntax, you can read more about it
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:
(-> (p/promise (ex-info "error" nil)) (p/catch (fn [error] (.log js/console error))))
catch function adds a new handler to the promise chain that will be called
when any of the previous promises in the chain are rejected or an exception is
catch function also returns a promise that will be resolved or
rejected depending on that will happen inside the catch hanlder.
If you prefer
map like parameters order, it there
err function (and
alias) that works in same way as
catch but has the parameters like
(->> (p/promise (ex-info "error" nil)) (p/error (fn [error] (.log js/console error))))
On the JVM platform the reject value is mandatory to be an instance of
For adding both success and error handlers to a promise at the same time you can use
(p/branch a-promise (fn [v] (println "Ok" v)) (fn [err] (println err)))
In some circumstances you will want wait a completion of few promises at same time, and promesa 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
(let [p (p/all [(do-some-io) (do-some-other-io)])] (p/then p (fn [[result1 result2]] (do-something-with-results result1 result2))))
It there are also circumstances where you only want arbitrary select of the first
resolved promise. For this case, you can use the
(let [p (p/any [(p/delay 100 1) (p/delay 200 2) (p/delay 120 3)])] (p/then p (fn [x] (.log js/console "The first one finished: " x))))
you can emulate the functionality using
delay like so:
(-> (p/delay 1000 "foobar") (p/then (fn [v] (println "Received:" v)))) ;; After 1 second it will print the message ;; to the console: "Received: foobar"
The promise library offers the ability to add a timeout to async operations thanks
(-> (some-async-task) (p/timeout 200) (p/then #(println "Task finished" %)) (p/catch #(println "Timeout" %)))
In case the async task is slow, in the example more that 200ms, the promise will be
rejected with timeout error and successfully captured with the
Additionally to the promise abstraction, this library also comes with lightweight abstraction for scheduling task to be executed at some time in future:
(p/schedule 1000 (fn  (println "hello world")))
This example shows you how you can schedule a function call to be executed 1 second in the future. It works in the same way for both plaforms (clj and cljs).
The tasks can be cancelled using its return value:
(def task (p/schedule 1000 #(do-stuff))) (p/cancel! task)
Because it is the state of the art and the most performant promise implementation.
Let see some home made benchmarks:
lib=promesa number=500 promesa: 25.144ms lib=promesa-raw number=500 promesa-raw: 15.646ms lib=es6 number=500 es6: 2426.458ms lib=zousan number=500 zousan: 114.634ms lib=goog number=500 goog: 239.821ms
promesa-raw lib refers to the raw usage of bluebird library
and as you can observe,
promesa has some overhead. But it is nothing
important if you compare it with the rest of libraries.
Unlike Clojure and other Clojure contrib libs, does not have many restrictions for contributions. Just open a issue or pull request.
promesa is open source and can be found on github.
You can clone the public repository with this command:
git clone https://github.com/funcool/promesa
To run the tests execute the following:
For the JVM platform:
And for JS platform:
./scripts/build node out/tests.js
You will need to have nodejs installed on your system.
promesa is licensed under BSD (2-Clause) license:
Copyright (c) 2015-2016 Andrey Antukh <firstname.lastname@example.org> 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.