on.exit but only if there is an error

Have anyone seen a good implementation of a function that works like on.exit but for errors? A on.error or if.error if you like. So in a function it would look something like this below:

my_f -> function(){
  on.error(runThisFunction())
  ...
}

I'm not looking for a tryCatch function as I'd like to avoid wrapping all of my code into that.

Here is some dummy code that doesn't work, but sort of gives an idea:

on_error <- function(x = returnValue()){
    tryCatch(expr = {
        message("eval expr")
        x}, 
    error = function(e){
        message("error")
        stop(e)
    }
  )
}

test_function <- function(x){
    on.exit(on_error())
    log(x)
}

test_function(1) # should work
test_function("a") # I need this to eval the error part in on_error

Wouldn't purrr::safely() do what you want?

No, it's more of a tryCatch, but invoked in the on.exit(). I will edit the question with some dummy code that doesn't work.

Sorry, I'm still confused.

You want a function that executes some operation, say log(), so that, if there is no error in running log(), you just return the result, but if there is an error in running log(), you run the alternative function on_error(), is that the idea?

If so, why are you not happy with:

main_function <- log
on_error <- function(e){
  message("There was an error:\n", e,"But we caught it")
}

test_function <- function(x){
  tryCatch(main_function(x),
           error = on_error)
}

test_function(1)
#> [1] 0
test_function("a")
#> There was an error:
#> Error in main_function(x): non-numeric argument to mathematical function
#> But we caught it

Created on 2023-12-04 with reprex v2.0.2

The wrapping in tryCatch() doesn't look like much overhead, why is it a problem? Considering you can always leave the actual code in main_function(). Or if you want to make it even more transparent:

with_error_catching <- function(f, on_error){
  function(x) tryCatch(main_function(x),
                       error = on_error)
}

test_function <- with_error_catching(main_function,
                                     on_error)

so you can write main_function() and just add the error catching as an afterthought.

1 Like

I agree that on the surface it doesn't look like there is a lot of overhead, but I have 26 packages that scrape all sorts of different sites from docker on DigitalOcean. I would like a one line solution I can add to the top of most of the functions, so I can get a message on telegram when there is an error. I still want the functions to fail like they would do under normal circumstances though. I think a solution similar to on.exit or on.exit(on_error())would be a simple and clean way to handle it.

you are trying for some kind of loggin failures of your function calls ?
this is a hacky idea I had

my_global_error_log <<- list()


log_me_if_error <- function(func){
 
   e <- substitute(func)
   result <-try(eval(e),silent = TRUE)
   if(inherits(result,"try-error")){
     my_global_error_log <<- append(my_global_error_log,
            paste("Error : " , toString(result), "\nfrom : ",toString(e),"\n"))
     result <- NA
   }
   result
   }

log_me_if_error(log(2))
log_me_if_error(log("a"))
log_me_if_error(log(4))
log_me_if_error(log("x"))
my_global_error_log

I've ended up with the solution below, even though I see it's not really recommended in the comments on SO.

Inspired by:

call_this_function_to_see_if_the_function_failed <- function() {
    if (exists(".Traceback", envir = globalenv())) {
        traceback_calls <- get(".Traceback", envir = globalenv())
        sys_calls <- sys.calls()
        
        # Check if the most recent call in the traceback is the current function call
        if (identical(sys_calls[[length(sys_calls)]], traceback_calls[[1]])) {
            message("An error occurred in my_function.")
        } else {
            message("The detected error is not related to my_function.")
        }
    } else {
        message("No error detected.")
    }
}

my_function <- function(x) {
    on.exit(call_this_function_to_see_if_the_function_failed(), add = TRUE)
    
    log(x)
}

# Example usage
my_function(1)   # Should not produce an error
my_function("a") # Will cause an error

If there's no legitimate reason your function would return NULL , you can check the return value from within on.exit.

myFunction <- function(){
  
  on.exit(if(is.null(returnValue())) message("Houston, we have an error"))
  
  # ...<rest of function>...
  
}
1 Like

There should never be a NULL returned in those functions, so this is probably not a bad idea for my use case. I think I'll go with this one, as I don't risk running into issues with .Traceback.

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.