The purrr_error_indexed class is thrown by [map()], [map2()], [pmap()], and friends. It wraps errors thrown during the processing on individual elements with information about the location of the error.

Structure

purrr_error_indexed has three important fields:

Let's see this in action by capturing the generated condition from a very simple example:

f <- function(x) {
  rlang::abort("This is an error")
} 

cnd <- rlang::catch_cnd(map(c(1, 4, 2), f))
class(cnd)

cnd$location

cnd$name

print(cnd$parent, backtrace = FALSE)

If the input vector is named, name will be non-NULL:

cnd <- rlang::catch_cnd(map(c(a = 1, b = 4, c = 2), f))
cnd$name

Handling errors

(This section assumes that you're familiar with the basics of error handling in R, as described in Advanced R.)

This error chaining is really useful when doing interactive data analysis, but it adds some extra complexity when handling errors with tryCatch() or withCallingHandlers(). Let's see what happens by adding a custom class to the error thrown by f():

#| error: true 
f <- function(x) {
  rlang::abort("This is an error", class = "my_error")
} 
map(c(1, 4, 2, 5, 3), f)

This doesn't change the visual display, but you might be surprised if you try to catch this error with tryCatch() or withCallingHandlers():

#| error: true
tryCatch(
  map(c(1, 4, 2, 5, 3), f),
  my_error = function(err) {
    # use NULL value if error
    NULL
  }
)

withCallingHandlers(
  map(c(1, 4, 2, 5, 3), f),
  my_error = function(err) {
    # throw a more informative error
    abort("Wrapped error", parent = err)
  }
)

That's because, as described above, the error that map() throws will always have class purrr_error_indexed:

tryCatch(
  map(c(1, 4, 2, 5, 3), f),
  purrr_error_indexed = function(err) {
    print("Hello! I am now called :)")
  }
)

In order to handle the error thrown by f(), you'll need to use rlang::cnd_inherits() on the parent error:

#| error: true
tryCatch(
  map(c(1, 4, 2, 5, 3), f),
  purrr_error_indexed = function(err) {
    if (rlang::cnd_inherits(err, "my_error")) {
      NULL
    } else {
      rlang::cnd_signal(err)
    }
  }
)

withCallingHandlers(
  map(c(1, 4, 2, 5, 3), f),
  purrr_error_indexed = function(err) {
    if (rlang::cnd_inherits(err, "my_error")) {
      abort("Wrapped error", parent = err)
    }
  }
)

(The tryCatch() approach is suboptimal because we're no longer just handling errors, but also rethrowing them. The rethrown errors won't work correctly with (e.g.) recover() and traceback(), but we don't currently have a better approach. In the future we expect to enhance try_fetch() to make this easier to do 100% correctly).

Finally, if you just want to get rid of purrr's wrapper error, you can resignal the parent error:

#| error: true
withCallingHandlers(
  map(c(1, 4, 2, 5, 3), f),
  purrr_error_indexed = function(err) {
    rlang::cnd_signal(err$parent)
  }
)

Because we are resignalling an error, it's important to use withCallingHandlers() and not tryCatch() in order to preserve the full backtrace context. That way recover(), traceback(), and related tools will continue to work correctly.



Try the purrr package in your browser

Any scripts or data that you put into this service are public.

purrr documentation built on Aug. 10, 2023, 9:08 a.m.