Slow down a reactive expression with debounce/throttle — debounce
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.
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 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)
}