Add an async and async testing framework.
This is a well-worn idea: use a `promise-channel` of `[result nil]` or `[nil error]` pairs. The `go-pair` and `<?` macros handle catching exceptions (important, given that synchronous CLJ code expects to throw rather than return an error promise or similar), allowing code like: ``` (go-pair (let [result (<? (pair-chan-fn))] (when (not result) (throw (Exception. "No result!"))) (transform result))) ``` to be expressed naturally. These are the equivalents of `async` and `await` in JS. The implementation is complicated by significant incompatibilities between CLJ and CLJS. The solution presented here takes care to separate the macro definitions into CLJ. Sadly, this requires namespacing the per-environment symbols explicitly; but we hope to minimize such code in files like this. The most significant restriction to this approach is that consumers must require the transitive dependencies of the macro-defining modules. See the included tests (both CLJ and CLJS) for the appropriate incantations (for pair-chan, core.async, and test).
This commit is contained in:
parent
ca62b7b5d2
commit
0a312b4f40
7 changed files with 134 additions and 12 deletions
|
@ -4,7 +4,9 @@
|
|||
:license {:name "Mozilla Public License Version 2.0"
|
||||
:url "https://github.com/mozilla/datomish/blob/master/LICENSE"}
|
||||
:dependencies [[org.clojure/clojurescript "1.9.89"]
|
||||
[org.clojure/clojure "1.8.0"]]
|
||||
[org.clojure/clojure "1.8.0"]
|
||||
[org.clojure/core.async "0.2.385"]
|
||||
[jamesmacaulay/cljs-promises "0.1.0"]]
|
||||
|
||||
:cljsbuild {:builds {:release {
|
||||
:source-paths ["src"]
|
||||
|
|
57
src/datomish/pair_chan.cljc
Normal file
57
src/datomish/pair_chan.cljc
Normal file
|
@ -0,0 +1,57 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
(ns datomish.pair-chan)
|
||||
|
||||
;; From https://github.com/plumatic/schema/blob/bf469889b730feb09448fd085be5828f28425b41/src/clj/schema/macros.clj#L10-L19.
|
||||
(defn cljs-env?
|
||||
"Take the &env from a macro, and tell whether we are expanding into cljs."
|
||||
[env]
|
||||
(boolean (:ns env)))
|
||||
|
||||
(defmacro if-cljs
|
||||
"Return then if we are generating cljs code and else for Clojure code.
|
||||
https://groups.google.com/d/msg/clojurescript/iBY5HaQda4A/w1lAQi9_AwsJ"
|
||||
[then else]
|
||||
(if (cljs-env? &env) then else))
|
||||
|
||||
;; It's a huge pain to declare cross-environment macros. This is awful, but making the namespace a
|
||||
;; parameter appears to be *even worse*. Note also that `go` is not in a consistent namespace...
|
||||
(defmacro go-pair [& body]
|
||||
"Evaluate `body` forms in a `go` block. Catch errors and return a
|
||||
pair chan (a promise channel resolving to `[result error]`)."
|
||||
`(if-cljs
|
||||
(let [pc-chan# (cljs.core.async/promise-chan)]
|
||||
(cljs.core.async.macros/go
|
||||
(try
|
||||
(cljs.core.async/>! pc-chan# [(do ~@body) nil])
|
||||
(catch js/Error ex#
|
||||
(cljs.core.async/>! pc-chan# [nil ex#]))))
|
||||
pc-chan#)
|
||||
(let [pc-chan# (clojure.core.async/promise-chan)]
|
||||
(clojure.core.async/go
|
||||
(try
|
||||
(clojure.core.async/>! pc-chan# [(do ~@body) nil])
|
||||
(catch Exception ex#
|
||||
(clojure.core.async/>! pc-chan# [nil ex#]))))
|
||||
pc-chan#)))
|
||||
|
||||
;; Thanks to David Nolen for the name of this macro! http://swannodette.github.io/2013/08/31/asynchronous-error-handling/.
|
||||
;; This version works a bit differently, though. This must be a macro, so that the enclosed <!
|
||||
;; symbols are processed by any enclosing go blocks.
|
||||
(defmacro <?
|
||||
"Expects `pc-chan` to be a channel or ReadPort which produces [value nil] or
|
||||
[nil error] pairs, and returns values and throws errors as per `consume-pair`."
|
||||
[pc-chan]
|
||||
`(if-cljs
|
||||
(consume-pair (cljs.core.async/<! ~pc-chan))
|
||||
(consume-pair (clojure.core.async/<! ~pc-chan))))
|
||||
|
||||
(defn consume-pair
|
||||
"When passed a [value nil] pair, returns value. When passed a [nil error] pair,
|
||||
throws error. See also `<?`."
|
||||
[[val err]]
|
||||
(if err
|
||||
(throw err)
|
||||
val))
|
37
src/datomish/test_macros.cljc
Normal file
37
src/datomish/test_macros.cljc
Normal file
|
@ -0,0 +1,37 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
(ns datomish.test-macros
|
||||
(:require
|
||||
[datomish.pair-chan]))
|
||||
|
||||
;; From https://github.com/plumatic/schema/blob/bf469889b730feb09448fd085be5828f28425b41/src/clj/schema/macros.clj#L10-L19.
|
||||
(defn cljs-env?
|
||||
"Take the &env from a macro, and tell whether we are expanding into cljs."
|
||||
[env]
|
||||
(boolean (:ns env)))
|
||||
|
||||
(defmacro if-cljs
|
||||
"Return then if we are generating cljs code and else for Clojure code.
|
||||
https://groups.google.com/d/msg/clojurescript/iBY5HaQda4A/w1lAQi9_AwsJ"
|
||||
[then else]
|
||||
(if (cljs-env? &env) then else))
|
||||
|
||||
;; It's a huge pain to declare cross-environment macros. This is awful, but making the namespace a
|
||||
;; parameter appears to be *even worse*.
|
||||
(defmacro deftest-async
|
||||
[name & body]
|
||||
`(if-cljs
|
||||
(cljs.test/deftest
|
||||
~(with-meta name {:async true})
|
||||
(cljs.test/async done#
|
||||
(->
|
||||
(datomish.pair-chan/go-pair ~@body)
|
||||
(cljs.core.async/take! (fn [v# e#]
|
||||
(cljs.test/is (= e# nil))
|
||||
(done#))))))
|
||||
(clojure.test/deftest
|
||||
~(with-meta name {:async true})
|
||||
(let [[v# e#] (clojure.core.async/<!! (datomish.pair-chan/go-pair ~@body))]
|
||||
(clojure.test/is (= e# nil))))))
|
|
@ -1,7 +1,7 @@
|
|||
(ns datomish.test
|
||||
(:require
|
||||
[doo.runner :refer-macros [doo-tests]]
|
||||
[doo.runner :refer-macros [doo-tests doo-all-tests]]
|
||||
[cljs.test :as t :refer-macros [is are deftest testing]]
|
||||
datomish.test.core))
|
||||
datomish.test-macros-test))
|
||||
|
||||
(doo-tests 'datomish.test.core)
|
||||
(doo-tests 'datomish.test-macros-test)
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
(ns datomish.test.core
|
||||
(:require
|
||||
[cljs.test :as t :refer-macros [is are deftest testing]]
|
||||
[datomish.core :as d]))
|
||||
|
||||
(deftest test-core
|
||||
(is (= 1 1)))
|
16
test/datomish/test_macros_test.clj
Normal file
16
test/datomish/test_macros_test.clj
Normal file
|
@ -0,0 +1,16 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
(ns datomish.test-macros-test
|
||||
(:require
|
||||
[datomish.pair-chan :refer [go-pair]]
|
||||
[datomish.test-macros :refer [deftest-async]]
|
||||
[clojure.core.async :as a]
|
||||
[clojure.test :as t :refer [is are deftest testing]]))
|
||||
|
||||
(deftest sync-test
|
||||
(is (= 1 1)))
|
||||
|
||||
(deftest-async async-test
|
||||
(is (= 1 1)))
|
17
test/datomish/test_macros_test.cljs
Normal file
17
test/datomish/test_macros_test.cljs
Normal file
|
@ -0,0 +1,17 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
(ns datomish.test-macros-test
|
||||
(:require-macros
|
||||
[datomish.pair-chan :refer [go-pair]]
|
||||
[datomish.test-macros :refer [deftest-async]]
|
||||
[cljs.core.async.macros])
|
||||
(:require [cljs.core.async]
|
||||
[cljs.test :refer-macros [is are deftest testing async]]))
|
||||
|
||||
(deftest sync-test
|
||||
(is (= 1 1)))
|
||||
|
||||
(deftest-async async-test
|
||||
(is (= 1 1)))
|
Loading…
Reference in a new issue