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.

Other Posts

  1. Improved Support for Foreign Libs in cljdocMay 2020
  2. Static Blogging, Some Lessons LearnedMay 2020
  3. Working with Firebase Documents in ClojureScriptSeptember 2019
  4. 4 Small Steps Towards Awesome Clojure DocstringsJanuary 2019
  5. Sustainable Open Source: Current EffortsJanuary 2018
  6. Maven SnapshotsJune 2017
  7. Requiring Closure NamespacesMay 2017
  8. Simple Debouncing in ClojureScriptApril 2017
  9. Making Remote WorkMarch 2017
  10. Just-in-Time Script Loading With React And ClojureScriptNovember 2016
  11. Props, Children & Component Lifecycle in ReagentMay 2016
  12. Om/Next Reading ListNovember 2015
  13. Parameterizing ClojureScript BuildsAugust 2015
  14. ClojureBridge BerlinJuly 2015
  15. Managing Local and Project-wide Development Parameters in LeiningenJune 2015
  16. Formal Methods at AmazonApril 2015
  17. (lisp keymap)February 2015
  18. CLJSJS - Use Javascript Libraries in Clojurescript With EaseJanuary 2015
  19. Why Boot is Relevant For The Clojure EcosystemNovember 2014
  20. S3-Beam — Direct Upload to S3 with Clojure & ClojurescriptOctober 2014
  21. Patalyze — An Experiment Exploring Publicly Available Patent DataOctober 2014
  22. Running a Clojure Uberjar inside DockerSeptember 2014
  23. Using core.async and Transducers to upload files from the browser to S3September 2014
  24. Emacs & VimJuly 2014
  25. Heroku-like Deployment With Dokku And DigitalOceanMarch 2014
  26. Woodworking MasterclassesFebruary 2014
  27. Early Adopters And Inverted Social ProofFebruary 2014
  28. Living SmallFebruary 2014
  29. Sending You a TelegramJanuary 2014
  30. Running a Marathon, Or NotJanuary 2014
  31. Code SimplicityJanuary 2014
  32. What do we need to know?December 2013
  33. Sculley's DiseaseDecember 2013
  34. A Resurrection PostDecember 2013
  35. A Trip To The USSeptember 2013
  36. Analytics DataApril 2013
  37. Asynchronous CommunicationApril 2013
  38. From Zero to Marathon in Six MonthtsMarch 2013
  39. Git Information in Fish Shell’s PromptDecember 2012
  40. When We Build StuffAugust 2012
  41. Models, Operations, Views and EventsJuly 2012
  42. The Twelve Factor AppJune 2012
  43. Paris And BackMay 2012
  44. A Friend Is Looking For A Summer InternshipMay 2012
  45. Kandan Team ChatMay 2012
  46. Entypo Icon SetMarch 2012
  47. Startups, This Is How Design WorksMarch 2012
  48. Hosting A Static Site On Amazon S3February 2012
  49. Exim4 Fix Wrongly Decoded Mail SubjectJanuary 2012