How to avoid SQLite callbacks with Core.async?

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.

4 Likes