fix: Wrap extended task invocation in promise_resolve() (#4225)

* fix: Wrap extended task invocation in `promise_resolve()`

* refactor: cleanup error handling and promise chain

* chore: add news entry
This commit is contained in:
Garrick Aden-Buie
2025-05-30 08:44:31 -04:00
committed by GitHub
parent 6df0bb9423
commit eac0eea886
2 changed files with 30 additions and 28 deletions

View File

@@ -23,6 +23,8 @@
* Shiny's Typescript assets are now compiled to ES2021 instead of ES5. (#4066) * Shiny's Typescript assets are now compiled to ES2021 instead of ES5. (#4066)
* `ExtendedTask` now catches synchronous values and errors and returns them via `$result()`. Previously, the extended task function was required to always return a promise. This change makes it easier to use `ExtendedTask` with a function that may return early or do some synchronous work before returning a promise. (#4225)
## Bug fixes ## Bug fixes
* Fixed a bug with modals where calling `removeModal()` too quickly after `showModal()` would fail to remove the modal if the remove modal message was received while the modal was in the process of being revealed. (#4173) * Fixed a bug with modals where calling `removeModal()` too quickly after `showModal()` would fail to remove the modal if the remove modal message was received while the modal was in the process of being revealed. (#4173)
@@ -31,6 +33,8 @@
* Updated the JavaScript used when inserting a tab to avoid rendering dynamic UI elements twice when adding the new tab via `insertTab()` or `bslib::nav_insert()`. (#4179) * Updated the JavaScript used when inserting a tab to avoid rendering dynamic UI elements twice when adding the new tab via `insertTab()` or `bslib::nav_insert()`. (#4179)
* Fixed an issue with `ExtendedTask` where synchronous errors would cause an error that would stop the current session. (#4225)
# shiny 1.10.0 # shiny 1.10.0
## New features and improvements ## New features and improvements

View File

@@ -130,14 +130,15 @@ ExtendedTask <- R6Class("ExtendedTask", portable = TRUE, cloneable = FALSE,
#' arguments. #' arguments.
invoke = function(...) { invoke = function(...) {
args <- rlang::dots_list(..., .ignore_empty = "none") args <- rlang::dots_list(..., .ignore_empty = "none")
call <- rlang::caller_call(n = 0)
if ( if (
isolate(private$rv_status()) == "running" || isolate(private$rv_status()) == "running" ||
private$invocation_queue$size() > 0 private$invocation_queue$size() > 0
) { ) {
private$invocation_queue$add(args) private$invocation_queue$add(list(args = args, call = call))
} else { } else {
private$do_invoke(args) private$do_invoke(args, call = call)
} }
invisible(NULL) invisible(NULL)
}, },
@@ -204,44 +205,41 @@ ExtendedTask <- R6Class("ExtendedTask", portable = TRUE, cloneable = FALSE,
rv_error = NULL, rv_error = NULL,
invocation_queue = NULL, invocation_queue = NULL,
do_invoke = function(args) { do_invoke = function(args, call = NULL) {
private$rv_status("running") private$rv_status("running")
private$rv_value(NULL) private$rv_value(NULL)
private$rv_error(NULL) private$rv_error(NULL)
p <- NULL p <- promises::promise_resolve(
tryCatch({ maskReactiveContext(do.call(private$func, args))
maskReactiveContext({ )
# TODO: Bounce the do.call off of a promise_resolve(), so that the
# call to invoke() always returns immediately?
result <- do.call(private$func, args)
p <- promises::as.promise(result)
})
}, error = function(e) {
private$on_error(e)
})
promises::finally( p <- promises::then(
promises::then(p, p,
onFulfilled = function(value, .visible) { onFulfilled = function(value, .visible) {
private$on_success(list(value=value, visible=.visible)) private$on_success(list(value = value, visible = .visible))
}, },
onRejected = function(error) { onRejected = function(error) {
private$on_error(error) private$on_error(error, call = call)
}
),
onFinally = function() {
if (private$invocation_queue$size() > 0) {
private$do_invoke(private$invocation_queue$remove())
}
} }
) )
promises::finally(p, onFinally = function() {
if (private$invocation_queue$size() > 0) {
next_call <- private$invocation_queue$remove()
private$do_invoke(next_call$args, next_call$call)
}
})
invisible(NULL) invisible(NULL)
}, },
on_error = function(err) { on_error = function(err, call = NULL) {
cli::cli_warn(
"ERROR: An error occurred when invoking the ExtendedTask.",
parent = err,
call = call
)
private$rv_status("error") private$rv_status("error")
private$rv_error(err) private$rv_error(err)
}, },