请在2024 年 Clojure 状态调查!中分享您的看法。

欢迎!请参阅关于页面以了解有关此内容的更多信息。

0
core.async

我希望看到一个 "go" 宏的变体,它立即执行直到第一个可停用的操作,而不是调度(实际上调用 "setTimeout" 两次)。这主要用于 CLJS,因为 CLJ 可以通过阻塞操作来应对,但它也可能同样有用。

我的用例

我目前使用 core.async 来组成 CSS 动画,尽管它与 "go" 工作得很好,但仍然需要密切关注操作顺序,因为 "go" 会将控制权交还给浏览器,而浏览器可能决定重新流或重绘屏幕,尽管动画尚未正确启动。

示例

`
(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)
    )))

`

此示例尝试创建一个 "Toast" (链接:1) DOM节点,该节点从底部开始动画,并在3秒后删除。实际的实现需要处理点击等操作,但示例足够简单,可以描述问题。

问题在于X处,go被调度并将控制权交给了浏览器。因此动画的“设置”部分被延迟,浏览器将toast dom节点以原始方式渲染。

简单的解决方案是将{{dom/append}}调用移动到go块中,在这个例子中这很容易,但在更复杂的情况下就困难了。另一个解决方案是在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)
      ))))

`

这在这里也很简单,但在更复杂的事情上就困难了。

我希望看到的是一个 "go!" 变体,它不是调度go块,而是直接开始执行。

`
(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)
     )))  

`

微妙的区别是,在我们准备好之前,浏览器没有机会做任何事情,而且无论如何都会等待。在调整了几个动画效果之后,我总是需要将东西移出或移入当前的go块中。

我明白这是一个相当具体的用例,core.async可能根本不是做这个的最佳方式,但到目前为止,我已经取得了巨大的成功,代码也易于理解。

我没有深入研究 core.async 的内部,但go!应该是一个相当简单的添加。如果这个更改被接受,我将开始调查并创建一个补丁,只是在这样做之前想确保没有反对意见。

希望我的意图明确。CSS动画可能有点令人费解,这可能导致弄不清楚问题真正所在。

(链接:1) http://www.google.com/design/spec/components/snackbars-toasts.html#snackbars-toasts-specs

3 个答案

0
评论由:thheller

我调查了这个问题,以了解在core.async方面需要做什么来促进这个变化,结果什么都没有。


(defmacro go!
  "就像go,只执行直到第一个put/take为止"
  [& body]
  `(let [c# (cljs.core.async/chan 1)
         f# ~(ioc/state-machine body 1 &env ioc/async-custom-terminators)
         state# (-> (f#)
                    (ioc/aset-all! cljs.core.async.impl.ioc-helpers/USER-START-IDX c#))]
     (cljs.core.async.impl.ioc-helpers/run-state-machine state#)
     c#))


到目前为止效果完美,完全可以作为库内使用,我开始认为这可能对CLJS来说是一个更好的“默认”选项,因为没有“线程”可以启动“go”。

正常的“go”执行流程是

go前的代码
setTimeout/nextTick
go内的代码,直到第一个take/put
setTimeout/nextTick

而go!的流程则是

go!前的代码
go!内的代码,直到第一个take/put
setTimeout/nextTick

鉴于JavaScript的非阻塞性,额外的setTimeout不会损害它,也不会提供太多好处。

由于无需对core.async进行更改,我可以简单地在我的库中保留这个宏。我的用例相当特定,如果您对此“通用”没有兴趣,请自由关闭这个问题。
0

评论由:lgs32a

作为默认配置,这会违反go的异步执行保证,这将是一个破坏性的更改。从我的用户角度来看的一些观察结果:为了可读性,我更愿意使用你提出的关于go的用法示例,而不是使用go-now,因为go表示其整个体的异步执行,而在我所提出的go-now中,我需要扫描体以找到一个可挂起的操作来决定要同步执行的部分。我发现很难说服自己存在真正复杂的问题,使得go-now比重构go更好。(我认为您不会在库代码中安全地依赖实现名称空间。:-))

0
参考: https://clojure.atlassian.net/browse/ASYNC-131 (由thheller报告)
...