How to replace cljs-http.client/get with js/fetch?

hi, I have a first project in Clojure, using the book Living Clojure, where cljs retrieves JSON from the clj server. The code works for me with the cljs-http library, however when I tried substituting js/fetch to get the JSON I couldn’t figure it out, looking for some insight.

Here’s a minimal example with cljs-http (that worked):

(ns cheshire-cat.core
  (:require [cljs-http.client :as http]
            [cljs.core.async :refer [<! go]]))

(defn ^:export init []
  (go
    (let [response (<! (http/get "/cheshire-cat"))]
;do stuff with (:body response))))

Here is what I tried with js/fetch:

(ns cheshire-cat.core)

(defn ^:export init []
    (let 
        [response (.fetch js/window "http://localhost:4000/cheshire-cat")]
      (-> response (.then #(.json %)) (.then (js/alert #(str %))))))

I was using clojure - How to I get the body text of a Response object returned by the fetch API in ClojureScript? - Stack Overflow as an example. This returns ‘something’, some kind of js object Promise related I guess, but I don’t understand how to get the JSON text from it.

I was really trying to follow the example at ClojureScript - Promise interop, but similarly I could get back some kind of Promise related object, but no JSON text.

Thanks for any advice!

I posted an example for fetch using js-await here.

Currently only available when using shadow-cljs though.

(defn init []
  (js-await [res (js/fetch "/cheshire-cat")]
    (js/console.log "res" res)
    (js-await [body (.json res)]
      (js/console.log "got some json" body))))

fetch returns a Promise which will provide the async result. You cannot access the result in a sync fashion. So, with your raw example maybe gets a bit cleaner with fn instead of #(). body is the JSON object result. You can only access it there, not from the init function that called all this.

(defn ^:export init []
  (-> (js/fetch "/cheshire-cat")
    (.then (fn [response]
             (-> (.json response)
               (.then (fn [body]
                        (js/console.log body)
                        )))))))

With promise chaining this could be written as

(defn ^:export init []
  (-> (js/fetch "/cheshire-cat")
    (.then #(.json %))
    (.then (fn [body]
             (js/console.log body)
             ))))
2 Likes

Thanks very much! That helps wrap my head around it. I got it working, I also realized I needed to convert the JSON body with (js->clj body) to call it as a cljs map.

I also found this link helpful, Promise.

I’m still curious about the experimental <p! macro in core.async, from this example ClojureScript - Promise interop but I feel like I should understand Promise first…

edit: OK, thanks to your reply I looked at <p! again, and I think my understanding is improving a little. This works:

(ns cheshire-cat.core
  (:require [cljs.core.async :refer [go]]
            [cljs.core.async.interop :refer-macros [<p!]]))

(defn ^:export init []
  (go 
    (let [response (<p! (js/fetch "cheshire-cat"))]
    ; JSON endpoint is a map where one of the keys is "status"
      (js/alert ((js->clj (<p! (.json response))) "status")))))

Which I think is pretty cool! Though I don’t know how this works with Promise errors etc. I suppose core.async relies on try/catch looking at their example.