by Dustin Getz on
Electric Clojure’s ability to blur the line between client and server in web apps has some mind bending implications, and many people have asked for more examples of how this changes the way we solve problems. Here’s an example that personally opened my own mind as to what is possible.
The Electric standard library includes a clock helper fn which our Two Clocks tutorial uses in a simple demonstration. The problem is, on mobile, when you’re streaming a server clock at 60fps, which the tutorial does on the first page, the network IO keeps your device radio on. It’s of course fine for 5 minutes. But what happens is, when you switch tabs or close your phone, the tab is still technically running in the background (throttled) and therefore the server will continue streaming the clock at 60fps or whatever the hardcoded upper limit is. And since the websocket web standard doesn’t have backpressure (wtf), there’s no way for the browser process to signal to the server to slow down, so the Electric tutorial app will happily saturate your radio with clock ticks, at which point your phone gets real hot in your pocket and your battery randomly dies midday.
So how did we fix it? By streaming the browser visibility state to the server, and adding a single if
statement around the clock so that it only runs when the browser tab is in the foreground:
; Electric v2
(defn -get-system-time-ms [& [_]] #?(:clj (System/currentTimeMillis) :cljs (js/Date.now)))
(e/def system-time-ms ; starts Pending on server
(if (= "visible" (e/client (->> (listen> js/document "visibilitychange")
(m/reductions {} nil) ; give an initial tick
(m/latest (fn [_] (.-visibilityState js/document)))
new)))
(new (m/sample -get-system-time-ms <clock)) ; turn clock on
(throw (Pending.)))) ; tab is hidden, no clock. (This guards NPEs in userland)
The trick – see that e/client
? This Electric definition is portable and works on client or server. If you ask for a server clock, it will will happily subscribe to browser visibility events from the server! Therefore, when the tab backgrounds, the visibilitychange
fires, and Electric sends the updated visibilityState
to the server, unmounting the server clock.
When some folks first see code like this, they can sometimes react negatively:
“I don't like mushing together back- and front-end code, at least when it comes to completely separate concerns like e.g. data fetching and rendering”
Okay but we are engineers doing engineering here and we are facing a problem. How would you fix it?