Library / SDK
funcool/promesa avatar
funcool/promesa

promesa: promises and concurrency helpers for Clojure without runtime dependencies

A promise library & concurrency toolkit for Clojure and ClojureScript.

548 stars65 forksClojureMPL-2.0

At a glance

What is it?
promesa is a promise library and concurrency toolkit for Clojure and ClojureScript that adds async/await style abstractions on top of futures and deferreds, plus helpers for executors and for the bulkhead and CSP patterns, and it ships with zero runtime external dependencies. Its own versioning is confusing, because the README documents version 12.0.1 while the newest GitHub release tag in the repository is from 2022.
Who is it for?
Adopt promesa if you write Clojure or ClojureScript and want async/await style composition, executor helpers and bulkhead or CSP patterns in a library that adds nothing to your runtime dependency tree. Do not adopt it if you need a maintained release trail, because the newest GitHub release tag in the repository is 10.0.571 from 2022 while the README and the build files name 12.0.1, so the version you get depends on how you resolve the dependency.
Can I use it commercially?
Yes, with conditions. MPL-2.0 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
Is it still maintained?
Yes. The repository last received commits 84 days ago.
What is it written in?
Mainly Clojure, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A promise library that adds nothing to your dependency tree

promesa is described as a promise library and concurrency toolkit for Clojure and ClojureScript, and the phrase that matters in the README is the last one: with 0 runtime external dependencies.

That constraint is unusual for a library in this space and it is the design decision everything else follows from. A concurrency library that ships a handful of transitive dependencies changes your tree, adds version conflicts and inherits release schedules. promesa adds none, so adopting it is a decision about code and not about supply chain.

The library exposes a bunch of useful syntactic abstractions that considerably simplify working with promises, in a very similar way as you would do it in JavaScript with async/await, and many helpers from executors to concurrency patterns, with bulkhead and CSP named explicitly. The topics on the repository make the lineage clear: promises, deferred, completablefuture, non-blocking, threads, virtual-threads, concurrency, async-await.

So there are two layers to understand. The first is the promise layer, which gives you a value that will be available later and a set of combinators over it. The second is the concurrency toolkit, which is the part that goes beyond futures: executors to run work on, a bulkhead to limit concurrency, and CSP style patterns for communicating sequential processes.

The audience is Clojure and ClojureScript developers who are coming from a promise based world, usually from JavaScript, and who want the same shape of code in a language whose default answer to asynchronous work is a future and a deref. The ClojureScript support is not incidental, since a promise library that only works on the JVM is of limited use to a language that compiles to both.

The documentation lives at funcool.github.io/promesa/latest/, and the README links to it as a detailed documentation site rather than repeating the API in the readme itself, which is the right choice for a library of this size.

Adding it to a project, and the version confusion

Installation is a deps.edn entry, and the README gives two routes.

The first is the normal Maven coordinate from Clojars:

clojure
funcool/promesa {:mvn/version "12.0.1"}

The second is a git coordinate, which is what you use if you want a specific commit rather than a published artifact:

clojure
funcool/promesa
{:git/sha "7d841fc"
 :git/tag "12.0.1"
 :git/url "https://github.com/funcool/promesa"}

Notice the version in both, 12.0.1, and compare it with the release history the repository reports. The recent releases listed are 10.0.571 from 2022-12-07, 10.0.570 from 2022-12-05 and 9.1.538 from 2022-11-22. So the newest tagged release is nearly two major versions behind the version the README tells you to use, and the tags in between are the same 10.0.57x line, which suggests a continuous re-release scheme rather than a hand picked version.

There is a third data point. The package.json in the repository says the version is 12.0.0, one patch behind the 12.0.1 in the README, and the npm scripts in that file are the test tooling rather than a published package, and the file describes test tooling for the repository rather than a published npm package. So the version numbers come from three places and they do not agree exactly.

None of this is a crisis, since the library is consumed as a Maven coordinate and Clojars is the source of truth for what resolves. But it does mean one thing for an adopter: you cannot infer the version you are running from the GitHub releases list, and the changelog in the repository, CHANGELOG.md, is the record you should consult when a behaviour changes between versions.

The last push to the repository was on 2026-07-31, so the tree is being worked on, and the Clojars badge in the README points at the latest published version rather than at a GitHub tag.

The promise API, and why the example only runs on the JVM

The quick demonstration in the README is three lines on a REPL:

clojure
(require '[promesa.core :as p])

(->> (p/promise 1)
     (p/map inc)
     (deref)
;; => 2

Read it carefully and the shape of the library comes clear. A promise is created with p/promise, a combinator like p/map transforms it, and deref on the result gives the value. The macros are in promesa.core, and the threading form is the Clojure idiom rather than a JavaScript one, which is what a Clojure developer expects.

There is a note attached to the example, and it is the single most important caveat in the README: the example only works on the JVM because of the evident lack of blocking primitives on the JS runtime. Dereferencing a promise blocks the calling thread, and the JavaScript runtime has no way to block a thread without blocking the whole event loop.

That is not a flaw in the library so much as a statement about the difference between the two runtimes. On the JVM, a promise is a value you can wait for, so the same library can offer both a blocking style and a non-blocking style, and the combinators are shared. On the JavaScript runtime only the non-blocking style is available, so any code that derefs a promise will deadlock or fail there.

For a library that markets itself as working in Clojure and ClojureScript, this is the practical constraint to hold on to: the combinator layer travels, the blocking layer does not, and a shared codebase that derefs promises is not cross platform.

The topic list mentions virtual threads as well, which tells you the project tracks the JVM's newer concurrency primitives, and the mention of completablefuture and deferred identifies the two JVM future types it wraps. Deferred in particular is the Clojure answer to CompletableFuture, and promesa gives it a promise-shaped API on top.

Executors, bulkhead and CSP: the toolkit beyond the promise

The README's own summary splits the offering in two, and the second half is the part that differentiates promesa from a thin promise wrapper.

The first half is the promise layer, described as syntactic abstractions that considerably simplify working with promises. The second is many helpers from executors to concurrency patterns, with bulkhead and CSP called out by name.

An executor helper matters because the JVM's default is to run a task on a common pool, and the library lets you decide which executor a piece of work runs on. For a Clojure application that mixes blocking JDBC calls with CPU work, that separation is the difference between a service that holds its threads and one that does not.

The bulkhead pattern is the one to understand in a sentence: it isolates a resource so that a failure or a slowdown in one part of the system cannot consume everything available to another. Applied to a promise library, a bulkhead is a limit on how many concurrent operations of a certain kind may be in flight, so a slow downstream service degrades one feature rather than the whole application. It is a pattern from the resilience literature rather than from the promise literature, and its presence here tells you the author was aiming at production services rather than at script glue.

CSP is the other named pattern, communicating sequential processes, which in a promise setting means chaining independent-looking steps that pass values to each other, the style popularised by Go's channels and CSP in general. In a promise library, the channels are the values passed between the steps, and the sequencing is the promise chain rather than a blocking receive.

Taken together, these are the features that make promesa a concurrency toolkit rather than just a promise binding. The cost is that the library is larger than the core promise API, and the documentation site is where the details of the executor and pattern helpers live, since the README does not enumerate them.

For comparison, the built-in CompletableFuture that the topics list names gives you futures with composition but none of the executor, bulkhead or CSP helpers, and it is JVM only, so a ClojureScript codebase could not use it. That difference is the honest comparison for this library.

Testing across three runtimes, and the tooling that goes with it

The testing section of the README is unusually complete for a project of this size, because the library supports three runtimes and each needs its own command.

The JVM tests are the simplest:

``` shell clojure -M:dev -m promesa.tests.main

code

The ClojureScript tests need a JavaScript toolchain, and the README shows the setup as three steps because the project pins the package manager:

shell corepack enable corepack install yarn install yarn run test ```

The package.json explains the pinning. The packageManager field pins yarn at 4.9.1 with a full integrity hash, which is the mechanism corepack uses to fetch a specific version, and there is a .yarnrc.yml and a .nvmrc in the tree alongside a yarn.lock. The scripts are a shadow-cljs build and a node run of the compiled tests, with a watch variant for development.

Babashka has its own command, bb test:bb, which is a lighter way to run the test suite, and the repository carries a .bb directory and a bb.edn file for it.

Three toolchains for one library is a maintenance cost, and it is the direct consequence of promising to support both Clojure and ClojureScript. The payoff is that the test suite actually runs the same code in three environments, which is how you find the blocking-primitive difference described earlier rather than discovering it in production.

The rest of the tree is a well equipped Clojure project: a deps.edn for dependencies, a shadow-cljs.edn for the ClojureScript build, a pom.xml for the Maven publish, build.clj and tools.clj for the build tasks, a doc.clj and a doc directory for the documentation, a scripts directory, a resources directory, a dev directory and a test directory. There is also a .clj-kondo directory, so the code is linted with clj-kondo, and a CHANGELOG.md at the root.

The documentation site itself is generated into the doc directory, which is why the README can stay short and point at funcool.github.io/promesa/latest/ instead of documenting the API inline.

MPL-2.0, one maintainer, and what to check before adopting

The licence is MPL-2.0, with the LICENSE file at the root of the repository. That is the file-level copyleft licence, which means that if you modify the library's own source files you must publish those modifications, while applications that merely depend on it are not affected. For most consumers that is invisible, and it is a licence chosen by a project that wants to be used in closed applications without giving up the improvements to the library itself.

The project is by funcool, a single organisation, and the README's contribution guidance is short: open an issue for a discussion first, and a pull request is more than welcome if there is a clear use case for the proposed enhancement. That policy, an issue before a patch, is a reasonable way for a one-person project to avoid spending review time on work it does not want.

The maintenance picture is mixed in a way that an adopter should weigh honestly. The last push was on 2026-07-31, so the code is being worked on, and the README documents 12.0.1, a version well past the 10.0.57x release tags that the repository's release list shows from December 2022. So the library is versioned ahead of its published tags in a way that makes the tag list a poor guide to what is current.

Two other things to check before you commit. The first is the Clojure and ClojureScript split, since the promise combinators travel but blocking does not, and any code that derefs a promise is JVM only. The second is the zero dependency claim, which is worth verifying against your own tree rather than taking on trust, since it is the property that makes the library cheap to adopt and the property most likely to change in a future major version.

For a team that already has a house pattern for async work, promesa is a small, self-contained addition. For a team that needs a published release trail to sign off on, the gap between the documented version and the release history is the thing to raise first.

Editorial conclusion

Adopt promesa if you write Clojure or ClojureScript and want async/await style composition, executor helpers and bulkhead or CSP patterns in a library that adds nothing to your runtime dependency tree. Do not adopt it if you need a maintained release trail, because the newest GitHub release tag in the repository is 10.0.571 from 2022 while the README and the build files name 12.0.1, so the version you get depends on how you resolve the dependency. Verify first which version your build resolves, since the README's deps.edn and git coordinates name 12.0.1 while package.json still says 12.0.0, that the code you write works on the JavaScript runtime as well as the JVM, because the documented deref example only runs on the JVM, and that your project can tolerate a Clojure library that publishes almost no GitHub releases.

Frequently asked questions

What is promesa?

promesa is a promise library and concurrency toolkit for Clojure and ClojureScript, providing syntactic abstractions that work like async/await in JavaScript, plus helpers for executors and for the bulkhead and CSP patterns, with zero runtime external dependencies.

How do I add promesa to a Clojure project?

Add the coordinate funcool/promesa with :mvn/version "12.0.1" to your deps.edn, or use the git coordinates with :git/sha, :git/tag and :git/url as shown in the README. The library is published on Clojars, and the README also points at a detailed documentation site.

Does promesa work on the ClojureScript runtime?

The promise combinators do, but the README notes that the example only works on the JVM because of the lack of blocking primitives on the JS runtime, since dereferencing a promise blocks. Any shared code that derefs promises is therefore not cross platform.

How do I run the promesa test suite?

The JVM tests run with clojure -M:dev -m promesa.tests.main, the ClojureScript tests need corepack enable, corepack install, yarn install and then yarn run test, and the Babashka tests run with bb test:bb.

What licence is promesa released under?

MPL-2.0, with the LICENSE file at the root of the repository. The repository is maintained by funcool, and the README asks contributors to open an issue for a discussion before sending a pull request.

Official sources

  1. funcool/promesa on GitHub
  2. License: MPL-2.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/funcool-promesa.svg)](https://hysenlabs.com/projects/funcool-promesa)

Was this useful?