This is a helper function that behaves like then
, however if
is.promising()
returns FALSE
then the handlers will be executed
immediately.
Arguments
- expr
An expression that evaluates to either a promise or a non-promise value.
- on_success
A function to be called when no error occurs synchronously or asynchronously. When invoked, the function will be called with a single argument: the resolved value. Optionally, the function can take a second parameter
.visible
if you care whether the promise was resolved with a visible or invisible value. Can return a value or a promise.- on_failure
A function to be called if an error occurs synchronously or asynchronously. Takes one argument: the error object. Can return a value or a promise to recover from the error, or throw a new error. If
on_failure
is provided and doesn't throw an error (or return a promise that fails) then this is the async equivalent of catching an error.- ...
Reserved for future use. Currently must be empty.
- tee
If
TRUE
, ignore the return value of the callback, and use the original value ofexpr
as the result. Foron_failure
withtee = TRUE
, the callback executes but the original error is re-thrown afterward.
Value
If
expr
evaluates to a promise, a promise with a single followup promise to handle theon_success
oron_failure
callbacks.If
expr
evaluates to a non-promise value, the result of the synchronous operation after being processed byon_success
oron_failure
.If a callback returns a promise, the result is always a promise.
Details
Execution paths:
If
expr
evaluates to a promise (p
), it will callp |> then(on_success, on_failure)
.If
expr
evaluates to a non-promise value (x
), it will callon_success(x)
.If
expr
throws an error (e
) during calculation, it will callon_failure(e)
.
In all cases, the on_success
and on_failure
callbacks are executed (when
provided).
Utility
This function is useful for writing functions that need to execute followup
behavior now or within a promise. This is different behavior than then()
where everything is made into a promise.
hybrid_then()
allows authors to keep synchronous execution on the same
tick without requiring the use of a followup promise. This is particularly
appealing for situations where the author does not control the execution flow
for items that may be either synchronous or asynchronous, such as within
{plumber2}
.
Error Handling
If no on_failure
callback is provided and an error occurs, the error is
re-thrown immediately (for synchronous errors) or propagated through the
returned promise (for asynchronous errors).
If an on_failure
callback is provided but it throws an error, that new
error replaces the original error. With tee = TRUE
, even if on_failure
executes successfully, the original error is still re-thrown.
Callback Return Values
Callbacks can return any value, including promises. If a callback returns a
promise, the entire hybrid_then()
call will return a promise, even if the
input was synchronous. This allows seamless transitions between synchronous
and asynchronous execution.
Examples
# Basic usage - works with both sync and async values
add_to <- function(x, k) {
hybrid_then(
x,
on_success = function(value) {
value + k
},
on_failure = function(err) {
message("Error: ", err$message)
NA_real_
}
)
}
# Synchronous
42 |> add_to(100)
#> [1] 142
#> [1] 142
# Synchronous error
add_to({stop("Bad input!")}, 8)
#> Error: Bad input!
#> [1] NA
#> Error: Bad input!
#> [1] NA
if (FALSE) { # \dontrun{
# Asynchronous
promise_resolve(42) |>
add_to(8) |>
then(print)
# When resolved...
#> [1] 50
# Error handling - asynchronous
promise_resolve(stop("Bad async input!")) |>
add_to(8) |>
then(print)
# When resolved...
#> Error: Bad async input!
#> [1] NA
# Chaining multiple operations
# (Move the `promise_resolve()` around to see sync vs async behavior)
1 |>
hybrid_then(on_success = \(x) x + 1) |>
hybrid_then(on_success = \(x) promise_resolve(x * 2)) |>
hybrid_then(on_success = \(x) x - 1) |>
hybrid_then(print)
# When resolved...
#> [1] 3
} # }