The :hoplon: ClojureScript Web Framework - http://hoplon.io/

that's how i think of "callback hell", the issue isn't the nesting per se (that's a symptom) but the explicit time dependencies and implicit value dependencies, which is why i don't think promises add much value (no pun intended) - .then() is obviously a very explicit statement about time


cells are implicit on time and explicit on value dependencies/relationships/transformations


which is very similar to the arguments against imperative programming, where you "step through" time with values being incidental at every step, rather than specifying an outcome or data shapes/relationships with time being implicit


and OO is just imperative programming re-organised, so the native browser DOM model always leads us back down the road to callback hell


if you want to really stretch the idea you could start invoking metaphors from quantum theory where you can never measure certain properties together perfectly (e.g. velocity and position) - i haven't seen an example of explicitly defining value and time relationships together, in a way that is guaranteed to be consistent at least

flyboarder 2018-01-31T05:21:26.000065Z

ok so I have a working “simple” state machine for async data flow

flyboarder 2018-01-31T05:21:51.000105Z

@flyboarder uploaded a file: https://clojurians.slack.com/files/U0ALQHJRF/F918CEA9Z/-.clj

flyboarder 2018-01-31T05:22:06.000249Z

^ setup a useful cell

flyboarder 2018-01-31T05:22:20.000152Z

@flyboarder uploaded a file: https://clojurians.slack.com/files/U0ALQHJRF/F90QKHS8Z/-.clj

flyboarder 2018-01-31T05:22:36.000137Z

^ implicit state scope and -tpl


wah, that second snippet is a bit more complex than the first 😛

flyboarder 2018-01-31T05:24:31.000064Z

its actually so simple! create multiple implicit variables and 4 basic formulas 😉


kk i'll read it



so is the idea here to make something like castra but more generalised?

flyboarder 2018-01-31T05:26:21.000235Z

idea is that you can go like this…

flyboarder 2018-01-31T05:28:04.000070Z

@flyboarder uploaded a file: https://clojurians.slack.com/files/U0ALQHJRF/F91BBP57C/-.clj




yeah i meant how when you use castra there's the loading/error/response cells


but that logic is tied into castra

flyboarder 2018-01-31T05:29:11.000180Z

yes very similar


rather than a standalone standard that you could plug into anything that needs to do async fetching/work


mmm, i see how i could find something like this useful


i'm still in the multiple cell paradigm


so i have something like this for sente


(defn send!
 [{:keys [event data success error spinny can-drop? processing? result]}]
 {:pre [(keyword? event) (or (keyword? spinny) (nil? spinny))]}
 (let [send-vec (if data [event data] [event])
       cb? (or result success processing?)
       spinny (or spinny :background-task)
       processing? (or processing? (j/cell true))
       result (or result (j/cell nil))
       success (or
                #(reset! result %))
       error (or
              (fn [r]
               (when (and @connectivity.internet/connected?= @connectivity.sente/connected?=)
                 "Something is wrong. Try refreshing the page. If this error persists please contact us."))
               (throw (js/Error. r))))]
  (if @connectivity.sente/connected?=
   (if cb?
    ; We only want a spinny wheel if there's a success callback waiting on a
    ; round trip.
    (let [s (spinny.state/+! spinny.state/state spinny)
          cb (fn [r]
               (if (taoensso.sente/cb-success? r)
                (success r)
                (error r))
               (reset! processing? false)
               (spinny.state/timeout! spinny.state/state s)))]
     (reset! processing? true)
     (@sente.state/chsk-send! send-vec sente.data/timeout cb))
    (@sente.state/chsk-send! send-vec))
   (let [m (str "Attempted to send data without an open websocket: " send-vec)]
    ; Websockets don't work in CI so avoid spamming logs or erroring out.
    (when-not env.data/testing?
     (if can-drop?
      (taoensso.timbre/debug m)
      (throw (js/Error. m))))))))

flyboarder 2018-01-31T05:30:46.000054Z

Im all for multiple cells, the above just creates am implicit state scope mostly for custom elements to implement


processing? is like *loading*


then there's result which i think is like your *data*


i don't have an explicit *empty* state though, i'd be checking that ad-hoc in context based on what's in result

flyboarder 2018-01-31T05:33:39.000081Z

very cool! I’m using *status* for things like validation/completeness


a standardised way to represent this stuff outside castra would pave the way for me to spin out a sente lib


the main thing stopping me is that i'm making my own conventions up as i go 😛

flyboarder 2018-01-31T05:35:35.000207Z

yeah I think the thing to note here is the common occurrence of contextual concepts like success error result


yes, also note that my implementation is a combination of cells and callbacks


the default callbacks simply insert data into a cell, but sometimes you need to do a little more co-ordination/preprocessing than that



flyboarder 2018-01-31T05:37:07.000216Z

yeah the whole (or cell (fn [])) thing is interesting


(defn +!
 {:pre [(d/conn? conn)]}
  {:event ::+!
   :spinny :blocking
   :data {:id (wheel.test.util/fake :project/id)}
   :success (fn [r]
              (let [project-datom (datascript.rethinkdb/ratom->datom (:ratom r))]
               (route.state/navigate! :project-scope {:project-id (:v project-datom)})
               (swap! conn #(d/db-with % [project-datom]))


once you create a new project, you get navigated to the project


and also it gets tracked in metrics

flyboarder 2018-01-31T05:38:39.000055Z

very cool!


but then a barebones setup looks like


(defn fetch-projects-auth-meta!
 {:pre [(j/cell? result)]}
  {:event :project/projects-auth-meta
   :result result}))


internally sente.wire/send! builds the missing callbacks


but the only way to know what callbacks to build is with a well defined set of states like what you're working on...


mine have just grown organically based on needs of my UI, i can't really say i "designed" them in a formal/general sense 😛


@flyboarder one nitpick is the not in loading doesn't account for processes that aggregate several async processes that all need to complete for a single "load"

flyboarder 2018-01-31T05:45:33.000047Z

I like your callbacks tho, with my approach I am leaving it up to the data cell to be a lense which can persist state back to whatever

flyboarder 2018-01-31T05:46:40.000245Z

@thedavidmeister right thats why loading! is an optional state method, nothing prevents (reset! *loading* :step/1)

flyboarder 2018-01-31T05:47:18.000074Z

it’s still a true value which delegates to the :loading option in the -tpl


ok cool


so it's just intended as fallback behaviour, to do a basic toggle

flyboarder 2018-01-31T05:48:35.000055Z

yep which is all most dom elements need to be able to do


yeah, and you can pass that around


i've got standard buttons that disable themselves and pick up a spinny wheel based on processing? cells

flyboarder 2018-01-31T05:50:39.000033Z

yep exactly what im going for :thumbsup:

flyboarder 2018-01-31T05:51:10.000090Z

i think *loading* makes sense within a custom element, same with *error*


but then i have other things that are a countdown instead of a boolean, and can be canceled


dunno if that fits

flyboarder 2018-01-31T05:53:19.000039Z

for sure! I would probably implement that over the *status* state

flyboarder 2018-01-31T05:53:50.000089Z

I also want to figure out the concept of expected state

flyboarder 2018-01-31T05:55:39.000048Z

for search results for example





flyboarder 2018-01-31T05:56:47.000179Z

very cool!




*status* i guess?


keyed-for-tpl is pretty important for these FYI


very easy for these async processes to lose track of what they're supposed to be tracking without a key


easy to delete the wrong thing, etc.


@flyboarder what would expected look like?


is it something that could leverage spec?

flyboarder 2018-01-31T07:29:44.000080Z

the idea was for things like a search where you expect a result but this is different from an empty state


would you ever have both expected and empty?


i don't think i totally understand, can you get a screenshot of something that does it?

kennytilton 2018-01-31T09:55:29.000422Z

I happen to be playing with my cells-based XHR handler and it has occurred to me that dataflow (DF) is so effective against callback hell because DF is indifferent to time. An XHR response arriving who-knows-when is no different than a user deciding to click their mouse or press a key: DF engines Just Propagate(tm) values.


@hiskennyness "shut up and propagate"


yeah totally, it's all the same from the DF perspective


we don't need a dozen ways to observe a dozen different types of events...


but we do have a dozen ways to handle a dozen types of data 😛

kennytilton 2018-01-31T09:59:25.000134Z

As long as the CBH developer can specify the value dependency (“I need the value from XHR-1 to do XHR-2”) declaratively, the composite XHR group will converge on the desired result (driven by haphazardly returning responses).


yeah, i mean ^^ those "resend invite" buttons are themselves dependent on websocket states before they even render, and then they actually wait for the new invite data to come in from a different websocket before they are "finished"


they push to one server and receive a response from a different one, but it's no biggie

kennytilton 2018-01-31T10:02:48.000280Z

“but we do have a dozen ways to handle a dozen types of data” Sure, but that code (each formula we write) is our application and easy to write in isolation. The killer is the propagation of change, and DF solves that.


well i prefer it that way


i need some tools 😉


we're not quite at the stage where AI can write everything for me

kennytilton 2018-01-31T10:04:08.000639Z

I like to think of myself as a bionic programmer.




but yeah, this whole page i'm working on that the moment is really handy to have cells for 🙂


it's not just "send a request and wait for response"


it's "send an invite and spin until that email address appears in one of multiple different possible places, based on broadcasts"


because the user could exist in the system already and be added straight away, or just be given an "invite" to redeem when they finally do sign up


"the composite XHR group will converge on the desired result" - this composite XHR group would be a PITA otherwise

kennytilton 2018-01-31T10:15:30.000397Z

I am coding up a solution to an XHR use case I found while exploring ReactiveX and it is funny how quickly the wheels came off when something got into me and I tried being just a little reactive. Time reared its head. Back to “all in”….


you can mix and match approaches, but make sure to quarantine it in a scope somewhere

flyboarder 2018-01-31T19:19:17.000518Z

@thedavidmeister so *expected* could be true or an integer then *empty* would be more like *missing*, or better (defc= *missing* (and *empty* *expected*))

flyboarder 2018-01-31T19:21:44.000676Z

something is not missing if you aren’t expecting a result, or if an empty result is still a valid result