(ns ird.async
"The channel contract every ird function follows, plus the tools to work
with it.
Every API function returns a core.async promise-chan that delivers exactly
one value: the parsed response map, or an ExceptionInfo describing the
failure (see ird.errors). Nothing is ever thrown out of an API function;
errors travel as values so a `go` block can decide what to do with them:
(go (let [r (<! (domains/check c \"example.ro\"))]
(if (error? r) (handle r) (use r))))
Inside a go block `<?` takes and rethrows errors, so ordinary try/catch
works; `go-try` is a go block that catches whatever its body throws and
delivers it as a value — write your own helpers with it. Outside go blocks
use `->promise` (or cljs.core.async/take!)."
(:require-macros [ird.async])
(:require [cljs.core.async :as async]))
(defn error?
"True when V is an error value (any js/Error, which includes ExceptionInfo)."
[v]
(instance? js/Error v))
(defn throw-err
"Throw V when it is an error value, else return it. `<?` expands to this."
[v]
(if (error? v) (throw v) v))
(defn promise->chan
"Adapt a JS Promise to ird's channel contract: a promise-chan delivering
the resolution (nil closes the channel), or the rejection reason as a value
— wrapped in a js/Error when it is not one already."
[p]
(let [c (async/promise-chan)]
(.then p
(fn [v] (if (nil? v) (async/close! c) (async/put! c v)))
(fn [e] (async/put! c (if (error? e)
e
(js/Error. (str "Promise rejected: " e))))))
c))
(defn ->promise
"Adapt an ird channel to a JS Promise: resolves with the delivered value,
rejects when that value is an error — for the edge of a :node-library
build, where JavaScript callers expect promises."
[ch]
(js/Promise. (fn [resolve reject]
(async/take! ch (fn [v] (if (error? v) (reject v) (resolve v)))))))