:dependencies [[org.clojure/core.async "0.4.500"]
[async-interop "0.1.0"]],
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.
This proposal is being tracked in https://clojure.atlassian.net/browse/ASYNC-230.
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). You can also get it on https://clojars.org/async-interop:
:dependencies [[org.clojure/core.async "0.4.500"]
[async-interop "0.1.0"]],
(:require
[cljs.core.async :refer [go]]
[async-interop.interop :refer-macros [<p!]])
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
.
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.
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close