Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/igrishaev/pact
Chaining values with ease
https://github.com/igrishaev/pact
clojure clojurescript manifold promise
Last synced: 3 months ago
JSON representation
Chaining values with ease
- Host: GitHub
- URL: https://github.com/igrishaev/pact
- Owner: igrishaev
- License: unlicense
- Created: 2022-02-13T08:22:36.000Z (almost 3 years ago)
- Default Branch: master
- Last Pushed: 2023-03-09T15:45:20.000Z (almost 2 years ago)
- Last Synced: 2023-03-25T14:08:09.085Z (almost 2 years ago)
- Topics: clojure, clojurescript, manifold, promise
- Language: Clojure
- Homepage:
- Size: 37.1 KB
- Stars: 33
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Pact
A small library for chaining values through forms. It's like a promise but much
simpler.Since 0.1.1, supports ClojureScript and its specific types (e.g. Promise).
## Installation
Lein:
```clojure
[com.github.igrishaev/pact "0.1.1"]
```Deps.edn
```clojure
{com.github.igrishaev/pact {:mvn/version "0.1.1"}}
```## How It Works
The library declares two universe handlers: `then` and `error`. When you apply
them to the "good" values, you propagate further. Applying the `error` for does
nothing. And vice versa: `then` for the "bad" values does nothing, but calling
`error` on "bad" values gives you a chance to recover the pipeline.By default, there is only one "bad" value which is an instance of `Throwable`
(`js/Error` in ClojureScript). Other types are considered positive ones. The
library carries extensions for such async data types as `CompletableFuture`,
`Manifold` and `core.async`. You only need to require their modules so they
extend the `IPact` protocol.## Examples
Import `then` and `error` macros, then chain a value with the standard `->`
threading macro. Both `then` and `error` accept a binding vector and an
arbitrary body.```clojure
(ns foobar
(:require
[pact.core :refer [then error]]))(-> 42
(then [x]
(-> x int str))
(then [x]
(str x "/hello")))"42/hello"
```If any exception pops up, the sequence of `then` handlers gets interrupted, and
the `error` handler gets into play:```clojure
(-> 1
(then [x]
(/ x 0))
(then [x]
(str x "/hello")) ;; won't be executed
(error [e]
(ex-message e)))"Divide by zero"
```The `error` handler gives you a chance to recover from the exception. If you
return a non-exceptional data in `error`, the execution will proceed from the
next `then` handler:```clojure
(-> 1
(then [x]
(/ x 0))
(error [e]
(ex-message e))
(then [message]
(log/info message)));; nil
```The `->` macro can be nested. This is useful to capture the context for a
possible exception:```clojure
(-> 1
(then [x]
(+ x 1))
(then [x]
(-> x
(then [x]
(/ x 0))
(error [e]
(println "The x was" x)
nil))));; The x was 2
;; nil
```Besides `then` and `error` macros, the library provides the `then-fn` and
`error-fn` functions. They are useful when you have a ready function that
processes the value:```clojure
(ns foobar
(:require
[pact.core :refer [then-fn error-fn]]))(-> 1
(then-fn inc)
(then-fn str));; "2"
(-> 1
(then [x]
(/ x 0))
(error-fn ex-message));; "Divide by zero"
```Chaining with `then` and `error` is especially good for maps as allowing
destructuring:```clojure
(-> {:db {...} :cassandra {...}};; Get a user from the database and attach it to the scope.
(then [{:as scope :keys [db]}]
(let [user (jdbc/get-by-id db :users 42)]
(assoc scope :user user)));; Having a user, get their last items from Cassandra cluster
;; and attach them to the scope.
(then [{:as scope :keys [cassandra user]}]
(let [items (get-user-items cassandra user)]
(assoc scope :items items)));; Do something more...
(then [...]
...))
```## Fast fail
To interrupt the chain of `then` handlers, either throw an exception or use the
`failure` function which is just a shortcut for raising a exception. The
function takes a map or a message with a map:```clojure
(ns foobar
(:require
[pact.core :refer [then error failure]]))(-> 1
(then [x]
(if (not= x 42)
(failure "It was not 42!" {:x x})
(+ 1 x)))
(error-fn ex-data));; {:x 1 :ex/type :pact.core/failure}
```## Supported types
The `core` namespace declares the `then` and `error` handlers for the `Object`,
`Throwable`, and `java.util.concurrent.Future` types. The `Future` values get
dereferenced when passing to `then`.The following modules extend the `IPact` protocol for asynchronous types.
### Completable Future (Clojure)
The module `pact.comp-future` handles the `CompletableFuture` class available
since Java 11. The module also provides its own `future` macro to build an
instance of `CompletableFuture`:```clojure
(-> (future/future 1)
(then [x]
(inc x))
(then [x]
(/ 0 0))
(error [e]
(ex-message e))
(deref))"Divide by zero"
```Pay attention: if you fed an instance of `CompletableFuture` to the threading
macro, the result will always be of this type. Thus, there is a `deref` call at
the end.Internally, the `then` handler calls for the `.thenApply` method if a future and
the `error` handler boils down to `.exceptionally`.### Manifold (Clojure)
The `pact.manifold` module makes the handlers work with the amazing Manifold
library and its types. The Pact library doesn't have Manifold dependency: you've
got to add it on your own.```clojure
[manifold "0.1.9-alpha3"]
``````clojure
(-> (d/future 1)
(then [x]
(/ x 0))
(error [e]
(ex-message e))
(deref))"Divide by zero"
```Under the hood, `then` and `error` handlers call the `d/chain` and `d/catch`
macros respectively.Once you've put an instance of Manifold deferred, the result will always be a
`Deferred`.### Core.async (Clojure + ClojureScript)
To make the library work with `core.async` channels, import the
`pact.core-async` module:```clojure
(ns foobar
(:require
[pact.core :refer [then error]]
[pact.core-async]
[clojure.core.async :as a]))
```Like Manifold, the `core.async` dependency should be added by you as well:
```clojure
[org.clojure/core.async "1.5.648"]
```Now you can chain channels through the `then` and `error` actions. Internally,
each handler takes exactly one value from a source channel and returns a new
channel with the result. For `then`, exceptions traverse the channels being
untouched. And instead, the `error` handler ignores ordinary values and affects
only exceptions. Quick demo:```clojure
(let [in (a/chan)
out (-> in
(then [x]
(/ x 0))
(error [e]
(ex-message e))
(then [message]
(str "<<< " message " >>>")))](a/put! in 1)
(a/ (js/Promise.resolve 1)
(then-fn inc)
(then [x]
(js/console.log x)))
```A better example with fetching an HTTP resource:
```clojure
(-> (js/fetch "https://some.api.com/data.json")
(then [response]
(.json response))
(then [data]
...)
(error [e]
(js/console.log ...)))
```## Testing
To run both Clojure and ClojureScript tests, execute `make test-all`. For the
ClojureScript tests, you need Node.js installed.© 2022 Ivan Grishaev