I would REALLY go into the promesa
route. Core.async is not that easy to use in ClojureScript, and it’ll not help you too much on multiple situations. It’s also a lot less cleaner and it does not integrate well with the JS ecosystem. I wrote a little bit about this here: ClojureScript vs clojure.core.async – Maurício Szabo
Your solution could be changed for:
(ns your.ns.here
(:require [promesa.core :as p]))
(defn <sql
"Creates a sql operation that returns a channel, allowsing for async/await like syntax.
Has been abstracted to handle variety of return types depending on sql op(eration)"
[{:keys [sql params op]}]
(let [out (p/deferred)
params (apply array params) ;; TODO - if this is not a sequence, handle it?
cb (fn [err res]
(this-as this
(if err
(p/reject! out (ex-info (str "Failed to run async query of type " (name op)) {:error :sql-error :res res}))
;; TODO nil - nothing coming back.
(cond
(= :insert op) (p/resolve! out (.-lastID this))
res (p/resolve! out (js->clj res :keywordize-keys true))
:else (p/resolve! out (js->clj this :keywordize-keys true))))))]
(case op
:all (.all db sql params cb)
:get (.get db sql params cb)
:insert (.run db sql params cb)
:run (.run db sql params cb))
out))
At first glance, nothing changed - you just replaced >!
with p/resolve!
or p/reject!
and promise-chan
with p/deferred
. But you can do the following now:
(p/let [res (<sql ...)
something-from-js-world (run-this-result res)]
(do-something-with something-from-js-world))
The code above will automatically “short-circuit” (if <sql
return an error, the whole code will be a promise with a failure, with the error so it’s easier to capture exceptions) and it’ll also integrate with JS (because res
is a promise, you can send it to JS libraries that expect a promise, and they will work correctly). You can also make queries in parallel and await for all of then with p/all
, for example, if you want.