How to try catch?

The package I am using doesn’t provide error for eg.

session := db.driver.New(...)

This throws error:

Trying to create session on closed driver

This is because I have already closed the driver knowingly. But the fact is that now I want to try...catch my session error:

try {
  session := db.driver.New(...)
} catch (err) {
  fmt.Println(err)
}

But obviously we have no such try...catch option. So, how can we try...catch on this scenario?

If I log the session, I get this:

&{0x14000020400}

I might be able to solve the issue if I am able to read them. Not sure how can I?
I also tried this:

fmt.Printf("%v %p\n", &session, session)

But this prints like:

0x1400060e030 0x1400060e050

If you don’t have an err variable and still want to check for the error, try to check for the zero value of the returned variable type.

1 Like

I can’t. It returns pointer reference like I have shown.

There is the possibility like that function just to allocate some resources, so basically nothing shoud happen to generate an error. In this case you will just check the errors produced by other calls of other functions using the session handler.
:thinking:

1 Like

There’s nothing I can do. Just this function causes issue but doesn’t have error in return. It just returns the pointer address. And I would be glad if I can somehow get readable value.

What is this package? Can you provide a link? I suspect that we’re missing some context around how this New function is used.

It has been my experience that panicking indicates a programming error (e.g. a struct field can be nil and the code tried to access the field without checking if it was nil, or accessed a slice out of bounds, etc.). Errors resulting from opening or using connections, files, or sessions, etc., should be returned as errors instead of panics because the same code could succeed by changing the state of the system outside of the program (e.g. creating a file if it didn’t exist, ensuring the network is online, etc.).

This is purely anecdotal and I’m sure there are exceptions, but my point is that the panic:

Might indicate that before you call driver.New, maybe you have to check to see if the driver is closed (is there a Closed or IsClosed method that you could check before calling New?). Or maybe after the driver is closed, maybe you have to create a new driver and then create a new session from the driver? We can help better if you provide the code :slight_smile:

There’s no such thing to check if db is closed and my point in asking this question as there’s no try...catch block and how can we tackle on such scenario. I wish there’s something though third party package doesn’t provide error in return but causing error we should be able to handle that which is missing.

And my point is that recovering from a panic is a red flag that you’re doing something wrong, and I’m trying to help you find out what to do about it. Go does not use try ... catch for error handling. To answer your question, this is how you recover from a panic:

session, err := func() (s *Session, err error) {
    defer func() {
        v := recover()
        if v == nil {
            return
        }
        if e, ok := v.(error); ok {
            err = e
            return
        }
        err = fmt.Errorf("%v", v)
        return
    }()
    return db.driver.New(...), nil
}()
if err != nil {
    // handle err
}
// do something with session.

But either:

  1. The API you’re using is a bad API, or
  2. You’re using the API wrong.
3 Likes

This is what I am looking for. Thanks.

Couldn’t see mark this as solution option. :frowning:

@skillian Can you please guide me more?

I want to handle error in main.go if some error raised from package level code:

/api/user.go
if someCondition {
  // mark as error
}
main.go
// error caught
// do something

I think I need to use context or goroutine? I am not sure how.

I can think of two ways you can have “package level” code:

  1. init functions:

    package main
    
    func init() {
        if someGlobalVariable != expectedValue {
            panic("something is wrong")
        }
    }
    
    func main() {
        // `init` function is run automatically before `main` is called.
    }
    
  2. Initialize global variables by calling an anonymous function:

    var someGlobalVariable = func() int {
        v, err := someFunctionThatCanFail()
        if err != nil {
            panic("something failed")
        }
        return v
    }()
    
    func main() {
        // if you get here, then `someGlobalVariable` is already set.
    }
    

I have only used either of these in one situation:

var currentUser = func() *user.User {
    me, err := user.Current()
    if err != nil {
        panic(fmt.Errorf("failed to get current user: %w", err))
    }
    return me
}()

func main() {
    defaultConfigPath := filepath.Join(currentUser.HomeDir, ".config", "myApp")
}

I think it’s something closer. Suppose, we hit POST /api/users request and the main.go serves the api and it’s already run. What I wanted here is when /api/user.go has error with something for eg error creating user and I want to do re configure main.go and call that function again to try to create user. Hope, you got my intension now.

If you’re looking for retry logic, you can just checked errors and retry up to n times. Here’s an example using a closure:

In the case of an HTTP request specifically, you could use a custom handler to retry once:

package main

import (
	"log"
	"net/http"
)

func main() {
	mux := http.NewServeMux()
	mux.Handle("/user", RetryHandler(CreateUser))
	log.Fatal(http.ListenAndServe(":8090", mux))
}

// Will call f() and retry once if it returns an error
func RetryHandler(f func(w http.ResponseWriter, r *http.Request) error) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		err := f(w, r)
		// We had a problem. Retry the request
		if err != nil {
			err = f(w, r)
		}
		// TODO: add global error handling logic here?
	})
}

func CreateUser(w http.ResponseWriter, r *http.Request) error {
	// Create user and return errors
	return nil
}

The caveat here is, of course, that you need to not write to w in CreateUser until you are sure there are no errors. You don’t want to start writing to w, and then return an error which would cause RetryHandler to then call the CreateUser handler again.

Stuff that might also be useful:

  • You could also add retryCount int as a param to RetryHandler.
  • You could also refactor RetryHandler to be generic and handle deserialization / serialization of objects so it just calls a function and then sends whatever that function returns to w. I’ve done this before since generics came out and made it easy.
  • You could add a backoff delay rather than retrying it immediately.
  • You could add global error handling as I noted in the code.

And finally, there are packages meant to help with retry stuff, such as:

You could give that a try.

the try-catch block is used to handle the exception or errors that may occur during the execution of program, it allow you to gracefully handle and recover from unexpected situations…

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