使用core.async调节功能

时间:2016-02-26 23:11:44

标签: clojure clojurescript core.async

应该限制函数的可能执行次数。因此,在调用函数之后,应该在一段时间内忽略任何重复调用。如果在此期间有呼叫,则应在该时间段之后执行最后一次呼叫。

这是我使用core.async的方法。这里的问题是,在通道c中总结了额外的呼叫。我需要一个内部只有一个位置的通道,它会被put覆盖!每次。

XmlPullParserException Binary XML file line #17<vector> tag requires viewportWidth > 0

用法:

(defn throttle [f time]
  (let [c (chan 1)]
    (go-loop []
      (apply f (<! c))
      (<! (timeout time))
      (recur))
    (fn [& args]
      (put! c (if args args [])))))

有没有人知道如何解决这个问题?

解决方案

(def throttled (throttle #(print %) 4000))
(doseq [x (range 10)]
   (throttled x))

; 0
;... after 4 seconds
; 9

4 个答案:

答案 0 :(得分:2)

您可以使用debounce功能。

我会在这里复制出来:

(defn debounce [in ms]
  (let [out (chan)]
    (go-loop [last-val nil]
      (let [val (if (nil? last-val) (<! in) last-val)
            timer (timeout ms)
            [new-val ch] (alts! [in timer])]
        (condp = ch
          timer (do (>! out val) (recur nil))
          in (recur new-val))))
    out))

仅当in未向ms发送消息时,才会将其发送的最后一个值转发到out频道。虽然in在发射之间没有足够长的暂停时继续发射,但是连续丢弃所有但最后消息。

我测试了这个功能。等待4秒然后打印出9,这几乎是你要求的 - 需要一些调整!

(defn my-sender [to-chan values]
  (go-loop [[x & xs] values]
           (>! to-chan x)
           (when (seq xs) (recur xs))))

(defn my-receiver [from-chan f]
  (go-loop []
           (let [res (<! from-chan)]
             (f res)
             (recur))))

(defn setup-and-go []
  (let [in (chan)
        ch (debounce in 4000)
        sender (my-sender in (range 10))
        receiver (my-receiver ch #(log %))])) 

这是debounce的版本,将根据问题的要求输出,立即为0,然后等待4秒,然后是9:

(defn debounce [in ms]
  (let [out (chan)]
    (go-loop [last-val nil
              first-time true]
             (let [val (if (nil? last-val) (<! in) last-val)
                   timer (timeout (if first-time 0 ms))
                   [new-val ch] (alts! [in timer])]
               (condp = ch
                 timer (do (>! out val) (recur nil false))
                 in (recur new-val false))))
    out)) 

我使用了log而不是print。您不能使用println/print的普通core.async函数。有关说明,请参阅here

答案 1 :(得分:2)

要解决您的频道问题,您可以使用带滑动缓冲区的chan:

user> (require '[clojure.core.async :as async])
nil
user> (def c (async/chan (async/sliding-buffer 1)))
#'user/c
user> (async/>!! c 1)
true
user> (async/>!! c 2)
true
user> (async/>!! c 3)
true
user> (async/<!! c)
3

这样只会在下一个时间间隔计算放入通道的最后一个值。

答案 2 :(得分:1)

这取自David Nolens blog's source code

(defn throttle*
  ([in msecs]
    (throttle* in msecs (chan)))
  ([in msecs out]
    (throttle* in msecs out (chan)))
  ([in msecs out control]
    (go
      (loop [state ::init last nil cs [in control]]
        (let [[_ _ sync] cs]
          (let [[v sc] (alts! cs)]
            (condp = sc
              in (condp = state
                   ::init (do (>! out v)
                            (>! out [::throttle v])
                            (recur ::throttling last
                              (conj cs (timeout msecs))))
                   ::throttling (do (>! out v)
                                  (recur state v cs)))
              sync (if last 
                     (do (>! out [::throttle last])
                       (recur state nil
                         (conj (pop cs) (timeout msecs))))
                     (recur ::init last (pop cs)))
              control (recur ::init nil
                        (if (= (count cs) 3)
                          (pop cs)
                          cs)))))))
    out))

(defn throttle-msg? [x]
  (and (vector? x)
       (= (first x) ::throttle)))

(defn throttle
  ([in msecs] (throttle in msecs (chan)))
  ([in msecs out]
    (->> (throttle* in msecs out)
      (filter #(and (vector? %) (= (first %) ::throttle)))
      (map second))))

您可能还想在频道中添加dedupe传感器。

答案 3 :(得分:0)

我需要传递一个函数来捕获args,因为我将其用于输入事件,并且传递了可变对象。 ?

(defn throttle-for-mutable-args [time f arg-capture-fn]
  (let [c (async/chan (async/sliding-buffer 1))]
    (async-m/go-loop []
      (f (async/<! c))
      (async/<! (async/timeout time))
      (recur))
    (fn [& args]
      (async/put! c (apply arg-capture-fn (or args []))))))

我用像

[:input
  {:onChange (util/throttle-for-mutable-args                                      
               500
               #(really-use-arg %)                                 
               #(-> % .-target .-value))}]