async-interop
async-interop copied to clipboard
Use JS promises with core.async.
= Current status
Merged into CLJS, please refer to https://clojurescript.org/guides/promise-interop for the official guide.
= Promise interop Filipe Silva 2019-10-03 :type: guides :toc: macro :icons: font
ifdef::env-github,env-browser[:outfilesuffix: .adoc]
toc::[]
[[notes]] == 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.
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:
[source,clojure]
:dependencies [[org.clojure/core.async "0.4.500"] [async-interop "0.1.4"]],
[source,clojure]
(:require [cljs.core.async :refer [go]] [async-interop.interop :refer [<p!]])
[[using-javascript-promises-directly]] == 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: [source,javascript]
Promise.resolve(42) .then(val => console.log(val));
ClojureScript: [source,clojure]
(.then (js/Promise.resolve 42) #(js/console.log %))
However, chained promise methods in ClojureScript results in cascading code. Using the https://cljs.github.io/api/cljs.core/#-GT[thread-first macro] we can can get back to more elegant code.
JavaScript: [source,clojure]
Promise.resolve(42) .then(val => console.log(val)) .catch(err => console.log(err)) .finally(() => console.log('cleanup'));
ClojureScript: [source,javascript]
(.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 https://github.com/GoogleChrome/puppeteer#usage[Puppeteer usage]:
JavaScript: [source,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: [source,clojure]
(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]] == Using Promises with core.async
ClojureScript offers excellent facilities for async programming in https://github.com/clojure/core.async[core.async].
One especially handy tool is the <p!
macro, that consumes a promise inside a https://clojure.github.io/core.async/#clojure.core.async/go[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: [source,clojure]
(: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 https://github.com/clojure/core.async[repository],
https://clojure.org/news/2013/06/28/clojure-clore-async-channels[rationale],
https://github.com/clojure/core.async/blob/master/examples/walkthrough.clj[code walkthrough],
and https://swannodette.github.io/2013/07/12/communicating-sequential-processes[blog post].