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)
* `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
* 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)
* Fixed an issue with `ExtendedTask` where synchronous errors would cause an error that would stop the current session. (#4225)
# shiny 1.10.0
## New features and improvements

View File

@@ -130,14 +130,15 @@ ExtendedTask <- R6Class("ExtendedTask", portable = TRUE, cloneable = FALSE,
#' arguments.
invoke = function(...) {
args <- rlang::dots_list(..., .ignore_empty = "none")
call <- rlang::caller_call(n = 0)
if (
isolate(private$rv_status()) == "running" ||
private$invocation_queue$size() > 0
) {
private$invocation_queue$add(args)
private$invocation_queue$add(list(args = args, call = call))
} else {
private$do_invoke(args)
private$do_invoke(args, call = call)
}
invisible(NULL)
},
@@ -204,44 +205,41 @@ ExtendedTask <- R6Class("ExtendedTask", portable = TRUE, cloneable = FALSE,
rv_error = NULL,
invocation_queue = NULL,
do_invoke = function(args) {
do_invoke = function(args, call = NULL) {
private$rv_status("running")
private$rv_value(NULL)
private$rv_error(NULL)
p <- NULL
tryCatch({
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)
})
p <- promises::promise_resolve(
maskReactiveContext(do.call(private$func, args))
)
promises::finally(
promises::then(p,
onFulfilled = function(value, .visible) {
private$on_success(list(value=value, visible=.visible))
},
onRejected = function(error) {
private$on_error(error)
}
),
onFinally = function() {
if (private$invocation_queue$size() > 0) {
private$do_invoke(private$invocation_queue$remove())
}
p <- promises::then(
p,
onFulfilled = function(value, .visible) {
private$on_success(list(value = value, visible = .visible))
},
onRejected = function(error) {
private$on_error(error, call = call)
}
)
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)
},
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_error(err)
},