Compare commits

...

1 Commits

4 changed files with 251 additions and 0 deletions

View File

@@ -84,6 +84,7 @@ export(fluidPage)
export(fluidRow)
export(formatStackTrace)
export(freezeReactiveValue)
export(getCurrentObserver)
export(getDefaultReactiveDomain)
export(getShinyOption)
export(h1)

View File

@@ -703,6 +703,10 @@ execCount <- function(x) {
# Observer ------------------------------------------------------------------
# The initial value of "current observer" is NULL (and will always be NULL,
# except when within the scope of the observe or observeEvent)
.globals$currentObserver <- NULL
Observer <- R6Class(
'Observer',
portable = FALSE,
@@ -814,6 +818,8 @@ registerDebugHook("observerFunc", environment(), label)
run = function() {
ctx <- .createContext()
.execCount <<- .execCount + 1L
.globals$currentObserver <- self
on.exit(.globals$currentObserver <- NULL) # On exit, set it back to NULL
ctx$run(.func)
},
onInvalidate = function(callback) {
@@ -904,6 +910,125 @@ registerDebugHook("observerFunc", environment(), label)
)
)
#' Return the current observer
#'
#' This function is useful when you want to access an observer's methods or
#' variables directly. For example, you may have logic that destroys or
#' suspends the observer (from within its own scope) on some condition.
#'
#' This function works by returning the observer that is currently being run
#' when \code{getCurrentObserver()} is called. If there is no observer being
#' run (for example, if you called it from outside of a reactive context),
#' it will always return \code{NULL}. There are a few subtleties, however.
#' Consider the following five situations:
#'
#' \enumerate{
#' \item \code{getCurrentObserver() #outside of a reactive context}
#' \item \code{observe({ getCurrentObserver() }) }
#' \item \code{observe({ (function(){ getCurrentObserver() })() )} }
#' \item \code{observe({ isolate({ getCurrentObserver() }) }) }
#' \item \code{observe({ reactive({ getCurrentObserver() }) }) }
#' }
#'
#' In (1), since you're outside of a reactive context, we've already
#' established that \code{getCurrentObserver()} will return \code{NULL}.
#' In (2), we have the "vanilla" case, in which \code{getCurrentObserver()}
#' is called directly from within the body of the \code{observe} call.
#' This returns that observer. So far, so good. The problem comes with
#' the last three cases -- should we be able to "retrieve" the outer
#' observer if we're inside an inner function's scope, or inside of an
#' \code{isolate} or a \code{reactive} block?
#'
#' Before we can even asnwer that, there is an important distinction to
#' be made here: are function calls, \code{reactive} calls and
#' \code{isolate} blocks the same \emph{type} of thing? As far as Shiny
#' is concerned, the answer is no. Shiny-specific things (like observers,
#' reactives and code inside of an \code{isolate} chunk) exist in what we
#' call reactive contexts. Each run of an observer or a reactive is
#' associated with a particular reactive context. But regular functions
#' have no relation to reactive contexts. So, while calling a regular
#' function inside of an observer does not change the reactive context,
#' calling a \code{reactive} or \code{isolate} certainly does.
#'
#' With this distinction in mind, we can refine our definition of
#' \code{getCurrentObserver()} as follows: it returns the observer (if any)
#' that is currently running, as long as it is called from within the
#' same reactive context that was created when the observer started
#' running. If the reactive context changed (most likely because of a
#' call to \code{reactive} or \code{isolate}), \code{getCurrentObserver}
#' will return \code{NULL}. (There is another common way that the reactive
#' context can change inside an observer, which is if there is a second,
#' nested observer. In this case, \code{getCurrentObserver()} will return
#' the second, nested observer, since that is the one that is actually
#' running at that time.)
#'
#' So to recap, here's the return value for each of the five situations:
#' \enumerate{
#' \item \code{NULL}
#' \item the observer
#' \item the observer
#' \item \code{NULL}
#' \item \code{NULL}
#' }
#'
#' Now, you may be wondering why \code{getCurrentObserver()} should't be able
#' to get the running observer even if the reactive context changes. This isn't
#' technically impossible. In fact, if you want this behavior for some reason,
#' you can set the argument \code{dig} to be \code{TRUE}, so that the function
#' will "dig" through the reactive contexts until it retrieves the one for the
#' observer and returns the observer.
#'
#' So, with \code{dig = TRUE}, here's the return value for each of the five
#' situations:
#' \enumerate{
#' \item \code{NULL}
#' \item the observer
#' \item the observer
#' \item the observer
#' \item the observer
#' }
#'
#' The reason that this is not the default (or even encouraged) is because
#' things can get messy quickly when you cross reactive contexts at will.
#' For example, the return value of a \code{reactive} call is cached and that
#' reactive is not re-run unless its reactive dependencies change. If that
#' reactive has a call to \code{getCurrentObserver()}, this can produce
#' undesirable and unintuitive results.
#'
#' @param dig If \code{FALSE} (default), \code{getCurrentObserver} will only
#' return the observer if it's invoked directly from within the observer's
#' body or from a regular function. If \code{TRUE}, it will always return
#' the observer (if it exists on the stack), even if it's invoked from
#' within a \code{reactive} or an \code{isolate} scope. See below for more
#' information.
#'
#' @return The observer (created with a call to either \code{observe} or to
#' \code{observeEvent}) that is currently running.
#'
#' @seealso \code{\link{observe}}
#'
#' @examples
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#' shinyApp(
#' ui = basicPage( actionButton("go", "Go")),
#' server = function(input, output, session) {
#' observeEvent(input$go, {
#' print(paste("This will only be printed once; all",
#' "subsequent button clicks won't do anything"))
#' getCurrentObserver()$destroy()
#' })
#' }
#' )
#' }
#' @export
getCurrentObserver <- function(dig = FALSE) {
o <- .globals$currentObserver
ctx <- getCurrentContext()
if (!dig && !is.null(o) && ctx$id != o$.ctx$id) o <- NULL
o
}
#' Create a reactive observer
#'
#' Creates an observer from the given expression.

View File

@@ -124,6 +124,7 @@ sd_section("Reactive constructs",
"makeReactiveBinding",
"observe",
"observeEvent",
"getCurrentObserver",
"reactive",
"reactiveFileReader",
"reactivePoll",

124
man/getCurrentObserver.Rd Normal file
View File

@@ -0,0 +1,124 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/reactives.R
\name{getCurrentObserver}
\alias{getCurrentObserver}
\title{Return the current observer}
\usage{
getCurrentObserver(dig = FALSE)
}
\arguments{
\item{dig}{If \code{FALSE} (default), \code{getCurrentObserver} will only
return the observer if it's invoked directly from within the observer's
body or from a regular function. If \code{TRUE}, it will always return
the observer (if it exists on the stack), even if it's invoked from
within a \code{reactive} or an \code{isolate} scope. See below for more
information.}
}
\value{
The observer (created with a call to either \code{observe} or to
\code{observeEvent}) that is currently running.
}
\description{
This function is useful when you want to access an observer's methods or
variables directly. For example, you may have logic that destroys or
suspends the observer (from within its own scope) on some condition.
}
\details{
This function works by returning the observer that is currently being run
when \code{getCurrentObserver()} is called. If there is no observer being
run (for example, if you called it from outside of a reactive context),
it will always return \code{NULL}. There are a few subtleties, however.
Consider the following five situations:
\enumerate{
\item \code{getCurrentObserver() #outside of a reactive context}
\item \code{observe({ getCurrentObserver() }) }
\item \code{observe({ (function(){ getCurrentObserver() })() )} }
\item \code{observe({ isolate({ getCurrentObserver() }) }) }
\item \code{observe({ reactive({ getCurrentObserver() }) }) }
}
In (1), since you're outside of a reactive context, we've already
established that \code{getCurrentObserver()} will return \code{NULL}.
In (2), we have the "vanilla" case, in which \code{getCurrentObserver()}
is called directly from within the body of the \code{observe} call.
This returns that observer. So far, so good. The problem comes with
the last three cases -- should we be able to "retrieve" the outer
observer if we're inside an inner function's scope, or inside of an
\code{isolate} or a \code{reactive} block?
Before we can even asnwer that, there is an important distinction to
be made here: are function calls, \code{reactive} calls and
\code{isolate} blocks the same \emph{type} of thing? As far as Shiny
is concerned, the answer is no. Shiny-specific things (like observers,
reactives and code inside of an \code{isolate} chunk) exist in what we
call reactive contexts. Each run of an observer or a reactive is
associated with a particular reactive context. But regular functions
have no relation to reactive contexts. So, while calling a regular
function inside of an observer does not change the reactive context,
calling a \code{reactive} or \code{isolate} certainly does.
With this distinction in mind, we can refine our definition of
\code{getCurrentObserver()} as follows: it returns the observer (if any)
that is currently running, as long as it is called from within the
same reactive context that was created when the observer started
running. If the reactive context changed (most likely because of a
call to \code{reactive} or \code{isolate}), \code{getCurrentObserver}
will return \code{NULL}. (There is another common way that the reactive
context can change inside an observer, which is if there is a second,
nested observer. In this case, \code{getCurrentObserver()} will return
the second, nested observer, since that is the one that is actually
running at that time.)
So to recap, here's the return value for each of the five situations:
\enumerate{
\item \code{NULL}
\item the observer
\item the observer
\item \code{NULL}
\item \code{NULL}
}
Now, you may be wondering why \code{getCurrentObserver()} should't be able
to get the running observer even if the reactive context changes. This isn't
technically impossible. In fact, if you want this behavior for some reason,
you can set the argument \code{dig} to be \code{TRUE}, so that the function
will "dig" through the reactive contexts until it retrieves the one for the
observer and returns the observer.
So, with \code{dig = TRUE}, here's the return value for each of the five
situations:
\enumerate{
\item \code{NULL}
\item the observer
\item the observer
\item the observer
\item the observer
}
The reason that this is not the default (or even encouraged) is because
things can get messy quickly when you cross reactive contexts at will.
For example, the return value of a \code{reactive} call is cached and that
reactive is not re-run unless its reactive dependencies change. If that
reactive has a call to \code{getCurrentObserver()}, this can produce
undesirable and unintuitive results.
}
\examples{
## Only run examples in interactive R sessions
if (interactive()) {
shinyApp(
ui = basicPage( actionButton("go", "Go")),
server = function(input, output, session) {
observeEvent(input$go, {
print(paste("This will only be printed once; all",
"subsequent button clicks won't do anything"))
getCurrentObserver()$destroy()
})
}
)
}
}
\seealso{
\code{\link{observe}}
}