I'd like to see a variant of the "go" macro that executes immediately until the first parkable operation instead of dispatching (effectively calling "setTimeout" twice). This is primarily for CLJS since CLJ can get away with blocking operations but it still might be useful there as well.
My Use-Case:
I currently use core.async to orchestrate CSS animations together and while it works perfectly fine with "go" it does require careful attention to detail on the order of operations since "go" will yield control back to the browser which may decide to reflow/repaint the screen although the animation wasn't properly started yet.
Example:
`
(defn show-toast [message]
(let [toast (create-toast-dom message)]
(dom/append js/document.body toast)
;; X - PROBLEM: REPAINT HAPPENS NOW
(go (<! (anim/slide-in-from-bottom animation-time toast))
(<! (timeout 3000))
(<! (anim/fade-out animation-time toast))
(dom/remove toast)
)))
`
This example tries to create a "Toast" (link: 1) DOM node which is animated in from the bottom and removed after 3 seconds. A real implementation would require handling clicks and so forth but the example is easy enough to describe the problem. The {{(anim/...)}} functions set the appropriate CSS and return a {{(async/timeout time)}} channel which lets us wait for the "completion".
The problem at X is that the go is dispatched and control is given back to the browser. Therefore the "setup" part of the animation is delayed and the toast dom node is rendered by the browser as is. Usually this means that the browser will do a repaint. This will have a "flicker" effect as the node appears and then disappears again and animates (depending on the default CSS).
The simple solution is to move the {{dom/append}} call into the go block, which is easy enough in this example but hard for more complex. Another solution is to start the animation outside the go:
`
(defn show-toast [message]
(let [toast (create-toast-dom message)]
(dom/append js/document.body toast)
(let [slide-in (anim/slide-in-from-bottom animation-time toast)]
;; REPAINT, but slide-in animation already started so no "flicker"
(go (<! slide-in)
(<! (timeout 3000))
(<! (anim/fade-out animation-time toast))
(dom/remove toast)
))))
`
Again, simple enough here, hard for more complex things.
What I would prefer to see is a "go!" variant which instead of dispatching the go block just starts executing it.
`
(defn show-toast [message]
(let [toast (create-toast-dom message)]
(dom/append js/document.body toast)
(go! (<! (anim/slide-in-from-bottom animation-time toast)) ;; REPAINT ON <!
(<! (timeout 3000))
(<! (anim/fade-out animation-time toast))
(dom/remove toast)
)))
`
The subtle difference is that the browser does not get a chance to do anything before we are ready and would wait anyway. After fine-tuning a few of those animation effects myself it always requires moving stuff out of or into go blocks as it stands now.
I'm aware that this is a rather specific use-case and core.async might not even be the best way to do this at all but so far I had great success with it and the code is very easy to reason about.
I have not looked too much into the internals of core.async but go!
should be a pretty simple addition. If this change is accepted I would start looking into things and create a patch, just wanted to make sure there were no objections before doing so.
Hope my intent is clear. CSS animations can be a bit counter-intuitive which might not make it obvious where the problem actually lies.
(link: 1) http://www.google.com/design/spec/components/snackbars-toasts.html#snackbars-toasts-specs