Liking cljdoc? Tell your friends :D

Promise interop

Notes

This repository follows up on a discussion had on the Clojurians clojurescript slack channel where it was noted that questions about promises were a recurring topic.

David Nolen provided a macro (provided here almost verbatim) and remarked that it could be a candidate for the core.async.interop namespace along with a post on the topic.

Meanwhile I’m putting this repository up with some tests and what I imagine could be a guide in https://clojurescript.org/guides/ (except for this "Notes" section).

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 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.

Can you improve this documentation?Edit on GitHub

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close