Better ClojureScript Node REPL Defaults

Hi there! Welcome back β€”Β to you as much as to me. It's been a while that I've published anything but here we go: a little quality of life improvement for anyone driving a Node.js REPL from ClojureScript.

The Problems

There's two issues I often run into when working with ClojureScript and Node.js REPLs:

  1. many values are async, resulting in a <#Promise> return value
  2. uncaught errors will cause the Node.js process to exit

The first applies to any kind of ClojureScript REPL while the second is a more Node-specific problem. Losing your REPL state whenever something fails is annoying. This behavior makes sense when you run Node.js in production but for a REPL... not ideal.

A Workaround

Fortunately a bandaid solution is pretty trivial. To solve 1) we can make use of the excellent portal tool. For 2) we can install a handler for unhandledRejection events, catching the error and reporting it in whatever way we like.

Below is a namespace that can be added to your :preloads or just required when you start a new REPL session.

(ns acme.node-repl-preloads
  (:require [portal.api :as portal]))

(js/process.on "unhandledRejection"
               (fn [err]
                 (js/console.log "unhandledRejection" err)
                 (tap> {:unhandledRejection err})))

(when (.-ACME_DEV js/process.env)
  (portal/open)
  (add-tap portal/submit))

I add this to my node-repl helper like this:

  (shadow/node-repl
    {:config-merge [{:devtools {:preloads '[acme.node-repl-preloads]}}]})

Now, with ACME_DEV set, we'll get a Portal window whenever we start a Node REPL, allowing us to chain promises into tap> and inspecting their value that way.

In addition to that any errors will also be logged to the console and to the Portal window β€” without crashing the process πŸ™‚ From where I stand this would be a good default behavior but messing with error handling obviously comes with it's own tradeoffs.

Adding another handler for unhandledException is probably a good idea.

Anyways, nice to be back. I hope this is a slight improvement to someone's setup πŸ€—

Other Posts

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