I would really like to better understand Core Async with Clojure; specifically, I want to remove my self from callback hell to see if I can make my clojurescript backend for a new project to be a bit clearer.
In my current project trunk, I’m building a “full stack” clojurescript application with electron, using SQLite as the database. Sqlite on node is built with callbacks in mind, and so some of my code requiring multiple database calls is starting to look pretty hairy:
(defn article-get "Fetches an article, and computes the `:word-data` for it. Sets `last_opened` value before fetching." [id cb] (let [q1-sql "UPDATE articles SET last_opened = ? WHERE article_id = ?" q1-params (array (js/Date.now) id) q2-sql "SELECT * FROM articles WHERE article_id = ?" q2-params (array id)] (.run db q1-sql q1-params (fn [err] (.get db q2-sql q2-params (fn [err row] (words-get-for-article (js->clj row :keywordize-keys true) cb)))))))
The above function is making two database calls: the first is to update the article Trunk is about to fetch, and update it’s “last opened” value to the current time. Then I can go ahead and select the value back in the second command. In fact, there is also a third db call (
words-get-for-article) but I’ll leave that out for the sake of brevity.
So, we have callbacks (and errors that I’m not sure how to handle yet). I’d really love to see how core.async could (if it can) be used to clean this up?
So far, I’ve found the chapter on core.async in Clojure for the Brave and True to be helpful but I haven’t gotten it adapted to work yet for this solution.
The example it provides looks like this:
(defn upper-caser [in] (let [out (chan)] (go (while true (>! out (clojure.string/upper-case (<! in))))) out)) (defn reverser [in] (let [out (chan)] (go (while true (>! out (clojure.string/reverse (<! in))))) out)) (defn printer [in] (go (while true (println (<! in))))) (def in-chan (chan)) (def upper-caser-out (upper-caser in-chan)) (def reverser-out (reverser upper-caser-out)) (printer reverser-out) (>!! in-chan "redrum") ; => MURDER (>!! in-chan "repaid") ; => DIAPER
Here, there are several defined global variables - which I won’t want to do. So, I’ll end up perhaps making some functions that perform the single db calls, but instead of returning the value, will return the channel they are happening on, and then I’ll string them all up in a single function?
(defn <article-update-last-opened [in] (let [out (chan) sql "UPDATE articles SET last_opened = ? WHERE article_id = ?"] (println "about to update last opened") (go (let [id (<! in) ;; this could be passed in? params (array (js/Date.now) id)] (.run db sql params (fn [err] (go (>! out id)))) out)))) (defn <article-get-by-id [in] (let [out (chan) sql "SELECT * FROM articles WHERE article_id = ?"] (println "about to select article") (go (let [id (<! in) ;; <1> there's nothing to take off the channel here. params (array )] (.get db sql params (fn [err row] (prn "value is " err row) (go (>! out row)))) out)))) (defn article-get-new [id] (let [db-chan (chan)] (go (>! db-chan id)) (-> db-chan <article-update-last-opened <article-get-by-id )))
I’m getting bit stuck here. The first function in
article-get-new runs (
<article-update-last-opened) but I think due to the callback in these functions being the place where the
out channel is getting a value placed on it – by the time we move onto the next threaded function, it’s too late (or rather… too soon?) to get a value out of the channel as the callback hasn’t executed yet.
I might be missing something simple here. What I’d love is for every database function to exist on it’s own, and then to have them be able to be composed into larger functions when necessary. Any advice / input would be appreciated.