Use the then function to access the eventual result of a promise (or, if
the operation fails, the reason for that failure). Regardless of the state of
the promise, the call to then is non-blocking, that is, it returns
immediately; so what it does not do is immediately return the result value
of the promise. Instead, you pass logic you want to execute to then, in the
form of function callbacks. If you provide an
onFulfilled callback, it will be called upon the promise's successful
resolution, with a single argument value: the result value. If you provide
an onRejected callback, it will be called if the operation fails, with a
single argument reason: the error that caused the failure.
Usage
then(promise, onFulfilled = NULL, onRejected = NULL, ..., tee = FALSE)
catch(promise, onRejected, ..., tee = FALSE)
finally(promise, onFinally)Arguments
- promise
A promise object. The object can be in any state.
- onFulfilled
A function that will be invoked if the promise value successfully resolves. When invoked, the function will be called with a single argument: the resolved value. Optionally, the function can take a second parameter
.visibleif you care whether the promise was resolved with a visible or invisible value. The function can return a value or a promise object, or can throw an error; these will affect the resolution of the promise object that is returned bythen().- onRejected
A function taking the argument
error. The function can return a value or a promise object, or can throw an error. IfonRejectedis provided and doesn't throw an error (or return a promise that fails) then this is the async equivalent of catching an error.- ...
Ignored.
- tee
If
TRUE, ignore the return value of the callback, and use the original value instead. This is useful for performing operations with side-effects, particularly logging to the console or a file. If the callback itself throws an error, andteeisTRUE, that error will still be used to fulfill the the returned promise (in other words,teeonly has an effect if the callback does not throw).- onFinally
A function with no arguments, to be called when the async operation either succeeds or fails. Usually used for freeing resources that were used during async operations.
Formulas
With {promises} depending on R >= 4.1, the shorthand of a formula, ~ fn(.) is no longer recommended by the {promises} package or tidyverse (for
example,
{purrr})
as we now have access to the function shorthand, \(x) fn(x). Please update
your code to use the new function shorthand syntax \(x) fn(x, arg1, args2)
instead of ~ fn(., arg1, arg2). The . can be confusing when chained with
other methods.
Chaining promises
The first parameter of then is a promise; given the stated purpose of the
function, this should be no surprise. However, what may be surprising is that
the return value of then is also a (newly created) promise. This new
promise waits for the original promise to be fulfilled or rejected, and for
onFulfilled or onRejected to be called. The result of (or error raised
by) calling onFulfilled/onRejected will be used to fulfill (reject) the
new promise.
promise_a <- get_data_frame_async()
promise_b <- then(promise_a, onFulfilled = head)In this example, assuming get_data_frame_async returns a promise that
eventually resolves to a data frame, promise_b will eventually resolve to
the first 10 or fewer rows of that data frame.
Note that the new promise is considered fulfilled or rejected based on
whether onFulfilled/onRejected returns a value or throws an error, not on
whether the original promise was fulfilled or rejected. In other words, it's
possible to turn failure to success and success to failure. Consider this
example, where we expect some_async_operation to fail, and want to consider
it an error if it doesn't:
promise_c <- some_async_operation()
promise_d <- then(promise_c,
onFulfilled = function(value) {
stop("That's strange, the operation didn't fail!")
},
onRejected = function(reason) {
# Great, the operation failed as expected
NULL
}
)Now, promise_d will be rejected if promise_c is fulfilled, and vice
versa.
Warning: Be very careful not to accidentally turn failure into success, if your error handling code is not the last item in a chain!
some_async_operation() |>
catch(function(reason) {
warning("An error occurred: ", reason)
}) |>
then(function() {
message("I guess we succeeded...?") # No!
})In this example, the catch callback does not itself throw an error, so the
subsequent then call will consider its promise fulfilled!
Convenience functions
For readability and convenience, we provide catch and finally functions.
The catch function is equivalent to then, but without the onFulfilled
argument. It is typically used at the end of a promise chain to perform error
handling/logging.
The finally function is similar to then, but takes a single no-argument
function that will be executed upon completion of the promise,
regardless of whether the result is success or failure. It is typically used
at the end of a promise chain to perform cleanup tasks, like closing file
handles or database connections. Unlike then and catch, the return value
of finally is ignored; however, if an error is thrown in finally, that
error will be propagated forward into the returned promise.
Visibility
onFulfilled functions can optionally have a second parameter visible,
which will be FALSE if the result value is invisible.