ClojureScript
Share your thoughts in the 2021 Clojure Community Survey!

Promise interop

Using JavaScript promises directly

Promises are a common way of handling asynchronous operations in JavaScript. You can just as easily use them in ClojureScript by calling the promise methods.

JavaScript:

Promise.resolve(42)
  .then(val => console.log(val));

ClojureScript:

(.then (js/Promise.resolve 42)
       #(js/console.log %))

However, chained promise methods in ClojureScript results in cascading code. Using the thread-first macro we can can get back to more elegant code.

JavaScript:

Promise.resolve(42)
  .then(val => console.log(val))
  .catch(err => console.log(err))
  .finally(() => console.log('cleanup'));

ClojureScript:

(.finally
  (.catch
  (.then (js/Promise.resolve 42)
          #(js/console.log %))
  #(js/console.log %))
  #(js/console.log "cleanup"))

; same as above
(-> (js/Promise.resolve 42)
    (.then #(js/console.log %))
    (.catch #(js/console.log %))
    (.finally #(js/console.log "cleanup")))

Promise-heavy code that uses await results in more complicated code structures that aren’t very friendly. Take this example from Puppeteer usage:

JavaScript:

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  try {
    await page.goto('https://example.com');
    await page.screenshot({path: 'example.png'});
  } catch (err) {
    console.log(err);
  }

  await browser.close();
})();

ClojureScript:

(def puppeteer (js/require "puppeteer"))

(-> (.launch puppeteer)
    (.then (fn [browser]
             (-> (.newPage browser)
                 (.then (fn [page]
                          (-> (.goto page "https://clojure.org")
                              (.then #(.screenshot page #js{:path "screenshot.png"}))
                              (.catch #(js/console.log %))
                              (.then #(.close browser)))))))))

To tame this sort of code we turn to core.async.

Using Promises with core.async

ClojureScript offers excellent facilities for async programming in core.async. One especially handy tool is the <p! macro, that consumes a promise inside a go block.

Using go blocks allows us to write code that looks synchronous even though it’s actually asynchronous, exactly like await and async do in JavaScript.

ClojureScript:

(:require
   [cljs.core.async :refer [go]]
   [cljs.core.async.interop :refer-macros [<p!]])

(def puppeteer (js/require "puppeteer"))

(go
  (let [browser (<p! (.launch puppeteer))
        page (<p! (.newPage browser))]
    (try
      (<p! (.goto page "https://clojure.org"))
      (<p! (.screenshot page #js{:path "screenshot.png"}))
      (catch js/Error err (js/console.log (ex-cause err))))
    (.close browser)))

This is just scratching the surface. core.async gives you very powerful queue-like channels that can do much more than handle one-off promises.

You can read more about core-async in the repository, rationale, code walkthrough, and blog post.

Original author: Filipe Silva