Slow down a reactive expression with debounce/throttle — debounce

v1.7.4|Source: R/reactives.R

Description

Transforms a reactive expression by preventing its invalidation signals from being sent unnecessarily often. This lets you ignore a very "chatty" reactive expression until it becomes idle, which is useful when the intermediate values don't matter as much as the final value, and the downstream calculations that depend on the reactive expression take a long time. debounce and throttle use different algorithms for slowing down invalidation signals; see Details.

debounce(r, millis, priority = 100, domain = getDefaultReactiveDomain())

throttle(r, millis, priority = 100, domain = getDefaultReactiveDomain())

Arguments

r

A reactive expression (that invalidates too often).

millis

The debounce/throttle time window. You may optionally pass a no-arg function or reactive expression instead, e.g. to let the end-user control the time window.

priority

Debounce/throttle is implemented under the hood using observers. Use this parameter to set the priority of these observers. Generally, this should be higher than the priorities of downstream observers and outputs (which default to zero).

domain

See domains.

Details

This is not a true debounce/throttle in that it will not prevent r from being called many times (in fact it may be called more times than usual), but rather, the reactive invalidation signal that is produced by r is debounced/throttled instead. Therefore, these functions should be used when r is cheap but the things it will trigger (downstream outputs and reactives) are expensive.

Debouncing means that every invalidation from r will be held for the specified time window. If r invalidates again within that time window, then the timer starts over again. This means that as long as invalidations continually arrive from r within the time window, the debounced reactive will not invalidate at all. Only after the invalidations stop (or slow down sufficiently) will the downstream invalidation be sent.

ooo-oo-oo---- => -----------o-

(In this graphical depiction, each character represents a unit of time, and the time window is 3 characters.)

Throttling, on the other hand, delays invalidation if the throttled reactive recently (within the time window) invalidated. New r invalidations do not reset the time window. This means that if invalidations continually come from r within the time window, the throttled reactive will invalidate regularly, at a rate equal to or slower than than the time window.

ooo-oo-oo---- => o--o--o--o---

Limitations

Because R is single threaded, we can't come close to guaranteeing that the timing of debounce/throttle (or any other timing-related functions in Shiny) will be consistent or accurate; at the time we want to emit an invalidation signal, R may be performing a different task and we have no way to interrupt it (nor would we necessarily want to if we could). Therefore, it's best to think of the time windows you pass to these functions as minimums.

You may also see undesirable behavior if the amount of time spent doing downstream processing for each change approaches or exceeds the time window: in this case, debounce/throttle may not have any effect, as the time each subsequent event is considered is already after the time window has expired.

Examples

## Only run examples in interactive R sessions
if (interactive()) {
options(device.ask.default = FALSE)

library(shiny)
library(magrittr)

ui <- fluidPage(
  plotOutput("plot", click = clickOpts("hover")),
  helpText("Quickly click on the plot above, while watching the result table below:"),
  tableOutput("result")
)

server <- function(input, output, session) {
  hover <- reactive({
    if (is.null(input$hover))
      list(x = NA, y = NA)
    else
      input$hover
  })
  hover_d <- hover %>% debounce(1000)
  hover_t <- hover %>% throttle(1000)

  output$plot <- renderPlot({
    plot(cars)
  })

  output$result <- renderTable({
    data.frame(
      mode = c("raw", "throttle", "debounce"),
      x = c(hover()$x, hover_t()$x, hover_d()$x),
      y = c(hover()$y, hover_t()$y, hover_d()$y)
    )
  })
}

shinyApp(ui, server)
}