简体   繁体   中英

Idiomatic way to delay showing spinner for http requests using core.async

I am buiding a simple web app using ClojureScript+Rum with multiple places where I need to show pieces of UI using data fetched from the server via http/get requests.

Often I want to show a spinner before data is fully loaded. But I don't want to show it immediately, as it could feel flickery (server could return response fast enough), so ideally it should show with some delay (say 300ms). So if browser gets response faster, the spinner would not show.

How to do this in an ideomatic core.async way? (see my attempt below).

It is trivial to start showing the spinner immediately as http request starts:

(ns my.app
    (:require
        ;; -- snip --
        [cljs-http.client :as http]
        [cljs.core.async :as ca]))

(defonce *state (atom {:loading false :data nil}))

(defn get-data! []
  (go
    (swap! *state assoc :loading true)
    (let [response (<! (http/get "/api/data"))
          data (:body response)]
      (swap! *state assoc :loading false :data data))))

;; -- render the data or spinner if :loading --

But how to delay showing the spinner? I tried doing it by "mixing" "timeout" and "response" channels together and then checking the value I am getting from result channel. It works, but the code feels clumsy:


(defonce *state (atom {:loading false :data nil}))

(defn timeout-return [out ms val]
  (go
    (<! (ca/timeout ms))
    (ca/put! out val)))

(defn http-get [out uri]
  (go
    (let [response (<! (http/get uri))]
      (ca/put! out response)
      )))

(defn get-data! []
  (go
    (let [t-out (ca/chan)
          resp-out (ca/chan)
          out (ca/chan)
          mix-out (ca/mix out)
          handle-timeout (fn [] (swap! *state assoc :loading true))
          handle-resp (fn [r] (swap! *state assoc :loading false :data (:body r)))]

      (ca/admix mix-out t-out)
      (ca/admix mix-out resp-out)

      (timeout-return t-out 400 :timeout)
      (http-get resp-out "/api/data")

      (let [r (<! out)]
        (if (= :timeout r)
          (do
            (handle-timeout)
            (handle-resp (<! out)))
          (handle-resp r)))
      )))

;; -- render the data or spinner if :loading --

Is there a better way to do this?

Personally I'd avoid using core.async unless your concurrency is complex enough that promises wouldn't be sufficient. There is a lot of overhead in creating and pulling from channels that standard js promises don't need to worry about.

For delaying the spinner, I'd use a debounce. Perhaps this would help: https://www.martinklepsch.org/posts/simple-debouncing-in-clojurescript.html

Otherwise you can just google how to implement a debounce.

Essentially you want to do this:

  1. Create a promise for your request
  2. Start a debounce task to: modify state to indicate loading
  3. on the promise .finally , Cancel any active debounce tasks & modify state to indicate that you're no longer loading

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM