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 秒后移除。实际实现需要处理点击等事件,但示例足够简单,足以描述问题。The {{(anim/...)}} 函数设置了适当的 CSS 并返回一个 {{(async/timeout time)}} 通道,它让我们等待 "完成"。

在 X 处的问题是 gor 已经分发并且控制权被交还给浏览器。因此,动画的 "设置" 部分被延迟,并且 toast DOM 节点被浏览器直接渲染。通常这意味着浏览器会进行重绘。这会产生“闪烁”效果,因为节点出现然后消失并动画化(取决于默认的 CSS)。

简单的方法是将 {{dom/append}} 调用移动到 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
直到第一个 take/put 的 go 中的代码
setTimeout/nextTick

使用 go! 时是

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

鉴于 JavaScript 的非阻塞特性,额外的 setTimeout 不会造成伤害,也不会提供很多好处。

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

评论由: lgs32a 发布

作为一个默认设置,这会违反 go 的异步执行保证,这将是一个破坏性的变更。从我的用户角度出发,一些观察:为了可读性,我宁愿使用你提出的 go 和 go-now 两个例子的用法,而不是 go-now,因为 go 表现了整个体异步执行,而 go-now 我需要扫描体以找到可暂停的操作来确定哪些部分会同步执行。我认为很难说服自己,可能存在如此复杂的问题,以至于 go-now 会比重构 go 更好。 (我认为你无法在库代码中安全地依赖 impl 命名空间 :-))

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