├── .gitignore ├── Makefile ├── README.md ├── UNLICENSE ├── project.clj ├── src └── pact │ ├── comp_future.clj │ ├── core.cljc │ ├── core_async.cljc │ └── manifold.clj ├── test └── pact │ └── core_test.cljc └── trash.clj /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | profiles.clj 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | /.lein-* 10 | /.nrepl-port 11 | /.prepl-port 12 | .hgignore 13 | .hg/ 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | .PHONY: test-all 3 | test-all: test test-js 4 | 5 | .PHONY: test 6 | test: 7 | lein test 8 | 9 | 10 | .PHONY: release 11 | release: 12 | lein release 13 | 14 | 15 | test-js: 16 | lein with-profile +cljs cljsbuild once 17 | node target/tests.js 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pact 2 | 3 | A small library for chaining values through forms. It's like a promise but much 4 | simpler. 5 | 6 | Since 0.1.1, supports ClojureScript and its specific types (e.g. Promise). 7 | 8 | ## Installation 9 | 10 | Lein: 11 | 12 | ```clojure 13 | [com.github.igrishaev/pact "0.1.1"] 14 | ``` 15 | 16 | Deps.edn 17 | 18 | ```clojure 19 | {com.github.igrishaev/pact {:mvn/version "0.1.1"}} 20 | ``` 21 | 22 | ## How It Works 23 | 24 | The library declares two universe handlers: `then` and `error`. When you apply 25 | them to the "good" values, you propagate further. Applying `error` to "good" things 26 | does nothing. And vice versa: `then` for the "bad" values does nothing, but calling 27 | `error` on "bad" values gives you a chance to recover the pipeline. 28 | 29 | By default, there is only one "bad" value which is an instance of `Throwable` 30 | (`js/Error` in ClojureScript). Other types are considered positive ones. The 31 | library carries extensions for such async data types as `CompletableFuture`, 32 | `Manifold` and `core.async`. You only need to require their modules so they 33 | extend the `IPact` protocol. 34 | 35 | ## Examples 36 | 37 | Import `then` and `error` macros, then chain a value with the standard `->` 38 | threading macro. Both `then` and `error` accept a binding vector and an 39 | arbitrary body. 40 | 41 | ```clojure 42 | (ns foobar 43 | (:require 44 | [pact.core :refer [then error]])) 45 | 46 | 47 | (-> 42 48 | (then [x] 49 | (-> x int str)) 50 | (then [x] 51 | (str x "/hello"))) 52 | 53 | "42/hello" 54 | ``` 55 | 56 | If any exception pops up, the sequence of `then` handlers gets interrupted, and 57 | the `error` handler gets into play: 58 | 59 | ```clojure 60 | (-> 1 61 | (then [x] 62 | (/ x 0)) 63 | (then [x] 64 | (str x "/hello")) ;; won't be executed 65 | (error [e] 66 | (ex-message e))) 67 | 68 | "Divide by zero" 69 | ``` 70 | 71 | The `error` handler gives you a chance to recover from the exception. If you 72 | return a non-exceptional data in `error`, the execution will proceed from the 73 | next `then` handler: 74 | 75 | ```clojure 76 | (-> 1 77 | (then [x] 78 | (/ x 0)) 79 | (error [e] 80 | (ex-message e)) 81 | (then [message] 82 | (log/info message))) 83 | 84 | ;; nil 85 | ``` 86 | 87 | The `->` macro can be nested. This is useful to capture the context for a 88 | possible exception: 89 | 90 | ```clojure 91 | (-> 1 92 | (then [x] 93 | (+ x 1)) 94 | (then [x] 95 | (-> x 96 | (then [x] 97 | (/ x 0)) 98 | (error [e] 99 | (println "The x was" x) 100 | nil)))) 101 | 102 | ;; The x was 2 103 | ;; nil 104 | ``` 105 | 106 | Besides `then` and `error` macros, the library provides the `then-fn` and 107 | `error-fn` functions. They are useful when you have a ready function that 108 | processes the value: 109 | 110 | ```clojure 111 | (ns foobar 112 | (:require 113 | [pact.core :refer [then-fn error-fn]])) 114 | 115 | (-> 1 116 | (then-fn inc) 117 | (then-fn str)) 118 | 119 | ;; "2" 120 | 121 | (-> 1 122 | (then [x] 123 | (/ x 0)) 124 | (error-fn ex-message)) 125 | 126 | ;; "Divide by zero" 127 | ``` 128 | 129 | Chaining with `then` and `error` is especially good for maps as allowing 130 | destructuring: 131 | 132 | ```clojure 133 | (-> {:db {...} :cassandra {...}} 134 | 135 | ;; Get a user from the database and attach it to the scope. 136 | (then [{:as scope :keys [db]}] 137 | (let [user (jdbc/get-by-id db :users 42)] 138 | (assoc scope :user user))) 139 | 140 | ;; Having a user, get their last items from Cassandra cluster 141 | ;; and attach them to the scope. 142 | (then [{:as scope :keys [cassandra user]}] 143 | (let [items (get-user-items cassandra user)] 144 | (assoc scope :items items))) 145 | 146 | ;; Do something more... 147 | (then [...] 148 | ...)) 149 | ``` 150 | 151 | ## Fast fail 152 | 153 | To interrupt the chain of `then` handlers, either throw an exception or use the 154 | `failure` function which is just a shortcut for raising a exception. The 155 | function takes a map or a message with a map: 156 | 157 | ```clojure 158 | (ns foobar 159 | (:require 160 | [pact.core :refer [then error failure]])) 161 | 162 | (-> 1 163 | (then [x] 164 | (if (not= x 42) 165 | (failure "It was not 42!" {:x x}) 166 | (+ 1 x))) 167 | (error-fn ex-data)) 168 | 169 | ;; {:x 1 :ex/type :pact.core/failure} 170 | ``` 171 | 172 | ## Supported types 173 | 174 | The `core` namespace declares the `then` and `error` handlers for the `Object`, 175 | `Throwable`, and `java.util.concurrent.Future` types. The `Future` values get 176 | dereferenced when passing to `then`. 177 | 178 | The following modules extend the `IPact` protocol for asynchronous types. 179 | 180 | ### Completable Future (Clojure) 181 | 182 | The module `pact.comp-future` handles the `CompletableFuture` class available 183 | since Java 11. The module also provides its own `future` macro to build an 184 | instance of `CompletableFuture`: 185 | 186 | ```clojure 187 | (-> (future/future 1) 188 | (then [x] 189 | (inc x)) 190 | (then [x] 191 | (/ 0 0)) 192 | (error [e] 193 | (ex-message e)) 194 | (deref)) 195 | 196 | "Divide by zero" 197 | ``` 198 | 199 | Pay attention: if you fed an instance of `CompletableFuture` to the threading 200 | macro, the result will always be of this type. Thus, there is a `deref` call at 201 | the end. 202 | 203 | Internally, the `then` handler calls for the `.thenApply` method if a future and 204 | the `error` handler boils down to `.exceptionally`. 205 | 206 | ### Manifold (Clojure) 207 | 208 | The `pact.manifold` module makes the handlers work with the amazing Manifold 209 | library and its types. The Pact library doesn't have Manifold dependency: you've 210 | got to add it on your own. 211 | 212 | ```clojure 213 | [manifold "0.1.9-alpha3"] 214 | ``` 215 | 216 | ```clojure 217 | (-> (d/future 1) 218 | (then [x] 219 | (/ x 0)) 220 | (error [e] 221 | (ex-message e)) 222 | (deref)) 223 | 224 | "Divide by zero" 225 | ``` 226 | 227 | Under the hood, `then` and `error` handlers call the `d/chain` and `d/catch` 228 | macros respectively. 229 | 230 | Once you've put an instance of Manifold deferred, the result will always be a 231 | `Deferred`. 232 | 233 | ### Core.async (Clojure + ClojureScript) 234 | 235 | To make the library work with `core.async` channels, import the 236 | `pact.core-async` module: 237 | 238 | ```clojure 239 | (ns foobar 240 | (:require 241 | [pact.core :refer [then error]] 242 | [pact.core-async] 243 | [clojure.core.async :as a])) 244 | ``` 245 | 246 | Like Manifold, the `core.async` dependency should be added by you as well: 247 | 248 | ```clojure 249 | [org.clojure/core.async "1.5.648"] 250 | ``` 251 | 252 | Now you can chain channels through the `then` and `error` actions. Internally, 253 | each handler takes exactly one value from a source channel and returns a new 254 | channel with the result. For `then`, exceptions traverse the channels being 255 | untouched. And instead, the `error` handler ignores ordinary values and affects 256 | only exceptions. Quick demo: 257 | 258 | ```clojure 259 | (let [in (a/chan) 260 | out (-> in 261 | (then [x] 262 | (/ x 0)) 263 | (error [e] 264 | (ex-message e)) 265 | (then [message] 266 | (str "<<< " message " >>>")))] 267 | 268 | (a/put! in 1) 269 | 270 | (a/ (js/Promise.resolve 1) 282 | (then-fn inc) 283 | (then [x] 284 | (js/console.log x))) 285 | ``` 286 | 287 | A better example with fetching an HTTP resource: 288 | 289 | ```clojure 290 | (-> (js/fetch "https://some.api.com/data.json") 291 | (then [response] 292 | (.json response)) 293 | (then [data] 294 | ...) 295 | (error [e] 296 | (js/console.log ...))) 297 | ``` 298 | 299 | 300 | ## Testing 301 | 302 | To run both Clojure and ClojureScript tests, execute `make test-all`. For the 303 | ClojureScript tests, you need Node.js installed. 304 | 305 | © 2022 Ivan Grishaev 306 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject com.github.igrishaev/pact "0.1.2-SNAPSHOT" 2 | 3 | :description 4 | "Chaining values with ease" 5 | 6 | :url 7 | "https://github.com/igrishaev/pact" 8 | 9 | :deploy-repositories 10 | {"releases" {:url "https://repo.clojars.org" :creds :gpg}} 11 | 12 | :license 13 | {:name "Unlicense" 14 | :url "https://unlicense.org/"} 15 | 16 | :release-tasks 17 | [["vcs" "assert-committed"] 18 | ["test"] 19 | ["change" "version" "leiningen.release/bump-version" "release"] 20 | ["vcs" "commit"] 21 | ["vcs" "tag" "--no-sign"] 22 | ["deploy"] 23 | ["change" "version" "leiningen.release/bump-version"] 24 | ["vcs" "commit"] 25 | ["vcs" "push"]] 26 | 27 | :dependencies 28 | [] 29 | 30 | :profiles 31 | {:cljs 32 | {:cljsbuild 33 | {:builds 34 | [{:source-paths ["src" "test"] 35 | :compiler {:output-to "target/tests.js" 36 | :output-dir "target" 37 | :main pact.core-test 38 | :target :nodejs}}]} 39 | 40 | :plugins 41 | [[lein-cljsbuild "1.1.8"]] 42 | 43 | :dependencies 44 | [[org.clojure/clojurescript "1.10.891"] 45 | [javax.xml.bind/jaxb-api "2.3.1"] 46 | [org.glassfish.jaxb/jaxb-runtime "2.3.1"]]} 47 | 48 | :dev 49 | {:dependencies 50 | [[org.clojure/clojure "1.10.1"] 51 | [manifold "0.1.9-alpha3"] 52 | [org.clojure/core.async "1.5.648"]]}}) 53 | -------------------------------------------------------------------------------- /src/pact/comp_future.clj: -------------------------------------------------------------------------------- 1 | (ns pact.comp-future 2 | (:refer-clojure :exclude [future]) 3 | (:require 4 | [pact.core :as p]) 5 | (:import 6 | java.util.function.Function 7 | java.util.function.Supplier 8 | java.util.concurrent.CompletableFuture)) 9 | 10 | 11 | (extend-protocol p/IPact 12 | 13 | CompletableFuture 14 | 15 | (-then [this func] 16 | (.thenApply this (reify Function 17 | (apply [_ x] 18 | (func x))))) 19 | 20 | (-error [this func] 21 | (.exceptionally this (reify Function 22 | (apply [_ e] 23 | (func (ex-cause e))))))) 24 | 25 | 26 | (defmacro future [& body] 27 | `(CompletableFuture/supplyAsync (reify Supplier 28 | (get [_] 29 | ~@body)))) 30 | -------------------------------------------------------------------------------- /src/pact/core.cljc: -------------------------------------------------------------------------------- 1 | (ns pact.core 2 | #?(:clj 3 | (:import java.util.concurrent.Future))) 4 | 5 | 6 | (defprotocol IPact 7 | 8 | (-then [this func]) 9 | 10 | (-error [this func])) 11 | 12 | 13 | (defn failure 14 | 15 | ([data] 16 | (failure "Failure" data)) 17 | 18 | ([message data] 19 | (throw (ex-info message (-> (or data {}) 20 | (merge {:ex/type ::failure})))))) 21 | 22 | 23 | #?(:cljs 24 | 25 | (extend-protocol IPact 26 | 27 | default 28 | 29 | (-then [this func] 30 | (try 31 | (func this) 32 | (catch :default e 33 | e))) 34 | 35 | (-error [this func] 36 | this) 37 | 38 | js/Error 39 | 40 | (-then [this func] 41 | this) 42 | 43 | (-error [this func] 44 | (try 45 | (func this) 46 | (catch :default e 47 | e))) 48 | 49 | js/Promise 50 | 51 | (-then [this func] 52 | (.then this func)) 53 | 54 | (-error [this func] 55 | (.catch this func)))) 56 | 57 | 58 | #?(:clj 59 | (extend-protocol IPact 60 | 61 | nil 62 | 63 | (-then [this func] 64 | (try 65 | (func this) 66 | (catch Throwable e 67 | e))) 68 | 69 | (-error [this func] 70 | this) 71 | 72 | Object 73 | 74 | (-then [this func] 75 | (try 76 | (func this) 77 | (catch Throwable e 78 | e))) 79 | 80 | (-error [this func] 81 | this) 82 | 83 | Throwable 84 | 85 | (-then [this func] 86 | this) 87 | 88 | (-error [this func] 89 | (try 90 | (func this) 91 | (catch Throwable e 92 | e))) 93 | 94 | Future 95 | 96 | (-then [this func] 97 | 98 | (let [[result e] 99 | (try 100 | [@this nil] 101 | (catch Throwable e 102 | [nil e]))] 103 | 104 | (if e 105 | e 106 | (-then result func)))) 107 | 108 | (-error [this func] 109 | this))) 110 | 111 | 112 | (defn then-fn [p func] 113 | (-then p func)) 114 | 115 | 116 | (defn error-fn [p func] 117 | (-error p func)) 118 | 119 | 120 | (defmacro then 121 | {:style/indent 1} 122 | [p [x] & body] 123 | `(then-fn ~p (^{:once true} fn [~x] ~@body))) 124 | 125 | 126 | (defmacro error 127 | {:style/indent 1} 128 | [p [e] & body] 129 | `(error-fn ~p (^{:once true} fn [~e] ~@body))) 130 | -------------------------------------------------------------------------------- /src/pact/core_async.cljc: -------------------------------------------------------------------------------- 1 | (ns pact.core-async 2 | (:require 3 | [pact.core :as p] 4 | #?(:clj [clojure.core.async :as a]) 5 | #?(:cljs [cljs.core.async :as a]))) 6 | 7 | 8 | (defn throwable? [e] 9 | (instance? #?(:clj Throwable :cljs js/Error) e)) 10 | 11 | 12 | (extend-protocol p/IPact 13 | 14 | #?(:clj clojure.core.async.impl.channels.ManyToManyChannel 15 | :cljs cljs.core.async.impl.channels.ManyToManyChannel) 16 | 17 | (-then [this func] 18 | (let [out 19 | (a/promise-chan (map (fn [x] 20 | (if (throwable? x) 21 | x 22 | (func x)))) 23 | identity)] 24 | 25 | (a/take! this (fn [x] 26 | (a/put! out x)) ) 27 | 28 | out)) 29 | 30 | (-error [this func] 31 | (let [out 32 | (a/promise-chan (map (fn [x] 33 | (if (throwable? x) 34 | (func x) 35 | x))) 36 | identity)] 37 | 38 | (a/take! this (fn [x] 39 | (a/put! out x)) ) 40 | 41 | out))) 42 | -------------------------------------------------------------------------------- /src/pact/manifold.clj: -------------------------------------------------------------------------------- 1 | (ns pact.manifold 2 | (:require 3 | [pact.core :as p] 4 | [manifold.deferred :as d])) 5 | 6 | 7 | (extend-protocol p/IPact 8 | 9 | manifold.deferred.IDeferred 10 | 11 | (-then [this func] 12 | (d/chain this func)) 13 | 14 | (-error [this func] 15 | (d/catch this func))) 16 | -------------------------------------------------------------------------------- /test/pact/core_test.cljc: -------------------------------------------------------------------------------- 1 | (ns pact.core-test 2 | (:require 3 | [pact.core 4 | :refer [then error then-fn error-fn failure] 5 | :include-macros true] 6 | 7 | ;; extenders 8 | #?(:clj 9 | [pact.comp-future :as future]) 10 | 11 | #?(:clj 12 | [pact.manifold]) 13 | 14 | [pact.core-async] 15 | 16 | #?(:clj [clojure.core.async :as a] 17 | :cljs [cljs.core.async :as a]) 18 | 19 | #?(:cljs 20 | [cljs.core.async.interop :refer-macros [ in 49 | (then [x] 50 | (+ 1 x)) 51 | (then [x] 52 | (+ 1 x)) 53 | (then [x] 54 | (str "+" x "+")))] 55 | 56 | (a/go 57 | (a/>! in 1) 58 | (is (= "+3+" (a/ in 68 | (then [x] 69 | (throw (ex-info "Divide by zero" {}))) 70 | (then [x] 71 | 42) 72 | (error [e] 73 | (ex-message e)) 74 | (then [message] 75 | (str "<<< " message " >>>")))] 76 | 77 | (a/go 78 | (a/>! in 1) 79 | (is (= "<<< Divide by zero >>>" (a/ in 89 | (then [x] 90 | (throw (new js/Error "err 1"))) 91 | (then [x] 92 | 42) 93 | (error [e] 94 | (throw (new js/Error "err 2"))) 95 | (error [e] 96 | (ex-message e)) 97 | (then [message] 98 | (str "<<< " message " >>>")))] 99 | 100 | (a/go 101 | (a/>! in 1) 102 | (is (= "<<< err 2 >>>" (a/ in 115 | (then [x] 116 | (+ 1 x)) 117 | (then [x] 118 | (+ 1 x)) 119 | (then [x] 120 | (str "+" x "+")))] 121 | 122 | (a/>!! in 1) 123 | (is (= "+3+" (a/ in 130 | (then [x] 131 | (/ x 0)) 132 | (then [x] 133 | 42) 134 | (error [e] 135 | (ex-message e)) 136 | (then [message] 137 | (str "<<< " message " >>>")))] 138 | 139 | (a/put! in 1) 140 | (is (= "<<< Divide by zero >>>" (a/ in 147 | (then [x] 148 | (/ x 0)) 149 | (then [x] 150 | 42) 151 | (error [e] 152 | (+ 1 (ex-message e))) 153 | (error [e] 154 | (ex-message e)) 155 | (then [message] 156 | (str "<<< " message " >>>")))] 157 | 158 | (a/put! in 1) 159 | (is (str/starts-with? (a/ (new js/Date 1000000000000) 172 | (then [date] 173 | (.toGMTString date))))))) 174 | 175 | (testing "test nil" 176 | 177 | (is (= "" 178 | (-> nil 179 | (error-fn str) 180 | (then-fn str))))) 181 | 182 | (testing "boolean" 183 | 184 | (is (= "false" 185 | (-> true 186 | (then-fn not) 187 | (then-fn str))))) 188 | 189 | (testing "string" 190 | 191 | (is (= "42/hello" 192 | (-> 42 193 | (then [x] 194 | (-> x int str)) 195 | (then [x] 196 | (str x "/hello")))))) 197 | 198 | (is (= "Divide by zero" 199 | (-> 42 200 | (then [x] 201 | (throw (ex-info "Divide by zero" {}))) 202 | (then [x] 203 | (+ x 1000)) 204 | (error [e] 205 | (ex-message e))))) 206 | 207 | #?(:clj 208 | (testing "future" 209 | 210 | (is (= 103 211 | (-> (future (+ 1 2)) 212 | (then [x] 213 | (+ x 100))))))) 214 | 215 | (testing "error in error handler" 216 | 217 | (is (= "class java.lang.String cannot be cast to class java.lang.Number" 218 | (-> 42 219 | (then [x] 220 | (throw (ex-info "Divide by zero" {}))) 221 | (error [e] 222 | (throw (ex-info "class java.lang.String cannot be cast to class java.lang.Number" {}))) 223 | (error [e] 224 | (ex-message e)) 225 | (then [message] 226 | (subs message 0 63)))))) 227 | 228 | (testing "stop on error" 229 | 230 | (let [e 231 | (-> 0 232 | inc 233 | inc 234 | (then [x] 235 | (throw (ex-info "Divide by zero" {}))) 236 | (then [x] 237 | (inc x)) 238 | (then [x] 239 | (throw (ex-info "foobar" {}))) 240 | (then [x] 241 | (inc x)) 242 | (then [x] 243 | (inc x)) 244 | (then [x] 245 | (inc x)))] 246 | 247 | (is (= "Divide by zero" (ex-message e)))))) 248 | 249 | 250 | (deftest test-failure-ok 251 | 252 | (let [res 253 | (-> 1 254 | (then [x] 255 | (inc x)) 256 | (then [x] 257 | (failure {:foo 42})) 258 | (error [e] 259 | (ex-data e)))] 260 | 261 | (is (= {:foo 42 :ex/type :pact.core/failure} 262 | res)))) 263 | 264 | 265 | (deftest test-mapping-ok 266 | 267 | (is (= {:a 1 :b 2 :c 3 :d 6} 268 | 269 | (-> {:a 1 :b 2} 270 | 271 | (then [{:as scope :keys [a b]}] 272 | (assoc scope :c (+ a b))) 273 | 274 | (then [{:as scope :keys [a b c]}] 275 | (assoc scope :d (+ a b c))))))) 276 | 277 | 278 | #?(:clj 279 | 280 | (deftest test-comp-future-ok 281 | 282 | (testing "simple" 283 | 284 | (let [fut 285 | (future/future 1) 286 | 287 | res 288 | (-> fut 289 | (then [x] 290 | (inc x)))] 291 | 292 | (is (= 2 @res)))) 293 | 294 | (testing "ex type for comp future" 295 | 296 | (let [fut 297 | (future/future 1) 298 | 299 | res 300 | (-> fut 301 | (then [x] 302 | (inc x)) 303 | (then [x] 304 | (/ 0 0)) 305 | (error [e] 306 | e))] 307 | 308 | (is (= java.lang.ArithmeticException 309 | (-> res deref class))) 310 | 311 | (is (= "Divide by zero" 312 | (-> res deref ex-message)))) 313 | 314 | (testing "recover" 315 | 316 | (let [fut 317 | (future/future 1) 318 | 319 | res 320 | (-> fut 321 | (then [x] 322 | (inc x)) 323 | (then [x] 324 | (/ 0 0)) 325 | (error [e] 326 | (ex-message e)) 327 | (then [message] 328 | (str "<<< " message " >>>")))] 329 | 330 | (is (= "<<< Divide by zero >>>" 331 | @res)))) 332 | 333 | (testing "error in error" 334 | 335 | (let [fut 336 | (future/future 1) 337 | 338 | res 339 | (-> fut 340 | (then [x] 341 | (inc x)) 342 | (then [x] 343 | (/ 0 0)) 344 | (error [e] 345 | (+ 1 (ex-message e))) 346 | (error [e] 347 | (ex-message e)) 348 | (then [message] 349 | (str "<<< " message " >>>")))] 350 | 351 | (is (str/starts-with? 352 | @res "<<< class java.lang.String cannot be cast to class java.lang.Number"))))))) 353 | 354 | 355 | #?(:clj 356 | 357 | (deftest test-manifold-ok 358 | 359 | (testing "simple" 360 | 361 | (let [res 362 | (-> (d/future 1) 363 | (then [x] 364 | (inc x)) 365 | (then-fn inc))] 366 | 367 | (d/deferred? res) 368 | 369 | (is (= 3 @res)))) 370 | 371 | (testing "recovery" 372 | 373 | (let [res 374 | (-> (d/future 1) 375 | (then [x] 376 | (/ x 0)) 377 | (error [e] 378 | (ex-message e)))] 379 | 380 | (d/deferred? res) 381 | 382 | (is (= "Divide by zero" @res)))) 383 | 384 | (testing "error in error" 385 | 386 | (let [res 387 | (-> (d/future 1) 388 | (then [x] 389 | (/ x 0)) 390 | (error [e] 391 | (+ 1 (ex-message e))) 392 | (error [e] 393 | (ex-message e)))] 394 | 395 | (d/deferred? res) 396 | 397 | (is (str/starts-with? 398 | @res "class java.lang.String cannot be cast to class java.lang.Number")))))) 399 | 400 | 401 | 402 | #?(:cljs 403 | 404 | (do 405 | 406 | (deftest test-promise-ok 407 | 408 | (async done 409 | 410 | (let [p 411 | (-> (js/Promise.resolve 1) 412 | (then-fn inc) 413 | (then [x] 414 | (str "<<< " x " >>>")))] 415 | 416 | (a/go 417 | (is (= "<<< 2 >>>" ( (js/Promise.resolve 1) 427 | (then [x] 428 | (throw (ex-info "error" {}))) 429 | (error [e] 430 | (ex-message e)))] 431 | 432 | (a/go 433 | (is (= "error" ( (js/Promise.resolve 1) 443 | (then [x] 444 | (throw (ex-info "error1" {}))) 445 | (error [e] 446 | (throw (ex-info "error2" {}))) 447 | (error [e] 448 | (ex-message e)))] 449 | 450 | (a/go 451 | (is (= "error2" (