---
title: "Result and Error Handling"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Result and Error Handling}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
```r
library(jobqueue)
q <- Queue$new()
```
## Reacting to Results
```r
my_job <- q$run({ 42 })
# === A 'jobqueue'-style callback ============
my_job$on('done', ~message('Result = ', .$result))
#> Result = 42
# === A 'promises'-style callback ============
my_job %...>% message('Result = ', .)
#> Result = 42
```
See also `vignette('hooks')`
## Output vs Result
When `expr` is finished evaluating, the result is assigned to `$output`.
```r
job <- q$run({ 42 })
job$output
#> [1] 42
```
By default, `$result` will return the exact same value as `$output`.
```r
job$result
#> [1] 42
```
However, while `$output` is a fixed value, `$result` is highly configurable. Most notably by the `reformat` and `signal` parameters.
## Reformatting Output
Use `reformat = function (job) {...}` to define what should be returned by `$result`.
> **Important**
>
> You may access `job$output` in your `reformat` function.
> DO NOT access `job$result`, which would start an infinite recusion.
`reformat` can be set in several places:
* `Queue$new(reformat = http_wrap)`
* `Job$new(reformat = http_wrap)`
* `$run(reformat = http_wrap)`
* `$reformat <- http_wrap`
### Transformation
```r
http_wrap <- function (job) {
result <- job$output
paste0('{"status":200,"body":{"result":', result, '}}')
}
job <- q$run({ 42 }, reformat = http_wrap)
cat(job$result)
#> {"status":200,"body":{"result":42}}
```
### Job Details
```r
with_id <- function (job) {
list(result = job$output, id = job$id)
}
job <- q$run({ 42 }, reformat = with_id, id = 'abc')
dput(job$result)
#> list(result = 42, id = "abc")
```
### Non-blocking
`$output` blocks until the Job is done. If you want `$result` to not block, you can return a placeholder value instead.
```r
reformat <- function (job) {
if (job$is_done) { job$output$result }
else { 'result not available yet' }
}
job <- q$run({ Sys.sleep(5); 42 }, reformat = reformat)
job$result
#> [1] "result not available yet"
job$state
#> [1] "running"
```
## Signaling Errors
By default, errors and interrupts will be caught by jobqueue and returned as a condition object from `$result`.
```r
expr <- quote(stop('error XYZ'))
job <- q$run(expr)
x <- job$result
inherits(x, 'condition')
#> TRUE
x
#> Error in `q$run(expr)`:
#> ! Error evaluating $expr on background process
#> Caused by error in `stop("error XYZ")`:
#> ! error XYZ
#> ---
#> Subprocess backtrace:
#> 1. base::eval(expr = expr, envir = vars, enclos = .GlobalEnv)
#> 2. base::stop("error XYZ")
#> 3. | base::.handleSimpleError(function (e) …
#> 4. global h(simpleError(msg, call))
```
If you want those errors to continue propagating, set `signal = TRUE`.
```r
job <- q$run(expr, signal = TRUE)
x <- job$result
#> Error in "q$run(expr, signal = TRUE)" :
#> ! Error evaluating $expr on background process
#> Caused by error in `stop("error XYZ")`:
#> ! error XYZ
```
These signals can be caught as usual by `try()` and `tryCatch()`.
```r
print_cnd <- function (cnd) cli::cli_text('Caught {.type {cnd}}.')
job <- q$run({ Sys.sleep(5) }, signal = TRUE)$stop()
res <- tryCatch(job$result, error = print_cnd, interrupt = print_cnd)
#> Caught an object.
res
#> NULL
job <- q$run({ stop('error XYZ') }, signal = TRUE)
res <- tryCatch(job$result, error = print_cnd, interrupt = print_cnd)
#> Caught an object.
res
#> NULL
```
You can also signal some conditions while catching others. Perhaps stopping a
job is part of the "normal" workflow and shouldn't be lumped in with other
errors.
```r
job <- q$run({ Sys.sleep(5) }, signal = 'error')$stop()
res <- tryCatch(job$result, error = print_cnd)
res
#>
job <- q$run({ stop('error XYZ') }, signal = 'error')
res <- tryCatch(job$result, error = print_cnd)
#> Caught an object.
res
#> NULL
```
## Promise Handling
You can pass a Job directly to any function that expects a 'promise' object,
such as `promises::then` or `promises::%...>%`. These functions are re-exported
by jobqueue, so calling `library(promises)` is optional.
```r
q$run({ 42 }) %...>% message
#> 42
```
By default, errors and interrupts are caught by jobqueue and used for promise
fulfillment.
```r
onFulfilled <- function (value) cli::cli_text('Fulfilled with {.type {value}}.')
onRejected <- function (err) cli::cli_text('Rejected with {.type {err}}.')
job <- q$run({ Sys.sleep(5) })$stop()
job %>% then(onFulfilled, onRejected)
#> Fulfilled with an object.
job <- q$run({ stop('error XYZ') })
job %>% then(onFulfilled, onRejected)
#> Fulfilled with an object.
```
Setting `signal = TRUE` will cause errors and interrupts be sent to the
promise's rejection handler instead.
```r
job <- q$run({ Sys.sleep(5) }, signal = TRUE)$stop()
job %>% then(onFulfilled, onRejected)
#> Rejected with an object.
job <- q$run({ stop('error XYZ') }, signal = TRUE)
job %>% then(onFulfilled, onRejected)
#> Rejected with an object.
```