Количество возможных выполнений функции должно быть ограничено. Поэтому после вызова функции любой повторный вызов следует игнорировать в течение определенного периода времени. Если между тем есть вызовы, последний должен быть выполнен по истечении указанного периода времени.

Вот мой подход к core.async. Проблема здесь в том, что в канале c суммируются дополнительные звонки. Мне нужен канал только с одной позицией внутри, которая будет отменена командой put! каждый раз.

(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

Есть у кого-нибудь идеи, как это исправить?

Решение

(defn throttle [f time]
  (let [c (chan (sliding-buffer 1))]
    (go-loop []
      (apply f (<! c))
      (<! (timeout time))
      (recur))
    (fn [& args]
      (put! c (or args [])))))
5
Anton Harald 27 Фев 2016 в 02:11

4 ответа

Лучший ответ

Чтобы решить вопрос о вашем канале, вы можете использовать чан со скользящим буфером:

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
Arthur Ulfeldt 26 Фев 2016 в 23:51

Мне нужно было передать функцию для захвата аргументов, потому что я использовал ее для события ввода, и она передавала изменяемый объект. 🤷

(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))}]
0
Jp_ 23 Май 2020 в 15:31

Вы можете использовать функцию 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, затем ждать четыре секунды, затем 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. См. здесь для объяснения.

2
Chris Murphy 1 Апр 2019 в 10:35

Это взято из Дэвида Исходный код блога Nolens:

(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.

1
ClojureMostly 27 Фев 2016 в 06:23