Promises in a ClojureScript REPL
Roman wrote a nice
post on working inside ClojureScript REPLs, also touching on
how to deal with promises. If you're unfamiliar, the problem is that in Javascript many
operations return promises and unlike in Clojure you cannot block
until the promise is resolved. Instead you have to handle
the resulting value asynchronously. So if you for instance use
fetch
that could look something like this:
(.then (js/fetch "https://jsonip.com/") prn)
This will use prn
to print the value of the resolved
promise. Sometimes you don't just want to print things though, the
real power of a REPL lies in reusing values and successively
building up just the shape of data you need.
One nice trick I learned from Sean Grove years ago is that you can
just use def
. This isn't something you'd do in
production code but it's zero-ceremony and very handy to capture
values.
(.then (js/fetch "https://jsonip.com/") #(def -r %))
After this you can evaluate the -r
symbol in your
REPL and it will give you the value of the
fetch
promise. Alternatively to def
we
could also use an atom
to store the return value.
(def s (atom nil))
(.then (js/fetch "https://jsonip.com/") #(reset! s %))
Ergonomics
Now that we know how we can access the resulting value of a promise, let's make it convenient. For convenience I basically want two things:
- Make it easy to wrap any promise-returning form to capture it's return value
- Make it easy to access the return values of multiple promises
What I came up with is a function I just named
t
which can be used like this:
(let [s (atom {})]
(defn t
([kw] (get @s kw))
([p kw] (.then p (fn [r] (swap! s assoc kw r) r)))))
(-> (js/fetch "https://jsonip.com/")
(t :jsonip))
When t
receives two arguments it will consider the
first argument a promise storing the resulting value in an atom
under the key provided as the second argument,
:jsonip
in this case.
This API is particularly nice when you consider that most editor
integrations provide the ability to evaluate the form around your
cursor. If I place my cursor within (t :jsonip)
and
evaluate this form I can look at the value the promise returned
without changing any of the code. I can also just continue
chaining with then
since t
returns the
original promise.
Another nice feature is that I can reuse the values for future
REPL evaluations by referring to them using forms like
(t :jsonip)
.
Obviously this is just one way but I liked how that simple 4 line function made working with promises in a REPL a lot more enjoyable.