When I start developing in ClojureScript one year ago, core.async was ( and surelly continues to be) the most adopted library for work with asynchronous code.
It is not very bad approach because core.async is a great library, but in my opion is not a good abstraction for represent a result of async computation because it does not has builtin mechanism for handling errors.
Due to no existence of well established, documented and high performance promise library for clojurescript until today, I have written promesa.
It uses the the native Promise as underlying implementation for ClojureScript and jdk>=8 completable futures as underlying implemention for Clojure.
This is a quick preview of the api
(require '[promesa.core :as p]) (->> (p/all [(do-some-async-job) (do-some-other-async-job)]) (p/map (fn [[result1 result2]] (do-stuff result1 result2))) (p/err (fn [error] (do-something-with-error error))))
Other of the amazing things that promesa library exposes, is the ability to compose async computations that looks synchronous:
(defn do-stuff  (p/let [x (p/promise 1) ;; do async operation _ (p/delay 1000) ;; emulate sleep y (p/promise 2)] ;; do an other async operation (+ x y))) ;; do the operation with results ;; of previous two async operations (p/map #(println %) (do-stuff))
The result of
let expression is an other promise that will be resolved with result of final operation or rejected in case of one of its operations is rejected.
This post only shows a little portion of the available api. If you want know more, the promesa documentation has a lot of examples.