Search code examples
godefer-keyword

How to return deferred errors


Looking at the following function

// JsonGet makes an HTTP Get call
func JsonGet(url string) ([]byte, int, error) {
    timeout := time.Duration(timeoutInSecs * time.Second)
    client := http.Client{
        Timeout: timeout,
    }

    request, err := http.NewRequest("GET", url, nil)
    request.Header.Set("Content-type", "application/json")

    if err != nil {
        return nil, 0, err
    }

    resp, err := client.Do(request)
    if err != nil {
        return nil, 0, err
    }
    // At this point we know we are successful so we can defer the close
    // https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-iii-36a1ab3d6ef1
    defer func(f io.Closer) {
        if err := f.Close(); err != nil {
            log.Println("Error Deferring resp.Body.Close (io.Closer)")
        }
    }(resp.Body)

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, 0, err
    }

    return body, resp.StatusCode, nil



}

I have questions about the defer func

  1. Is there a way instead of logging the error to actually return the error as part of the return of the function?
  2. How would you change this function to accommodate that?
  3. What is an appropriate state or action to take for an error that occurs at deferment. In otherwords, let's say that the original function succeeds because it processes a request. Does the close happen after the return if successful. In other words, is there ever a situation where this function might return results then attempts to execute the defer and errors but has already returned?

Solution

  • Is there a way instead of logging the error to actually return the error as part of the return of the function?

    Yes you can let the deferred function modify the contents of the return values, if the function defined takes a named return value (Rule: 3 of https://blog.golang.org/defer-panic-and-recover) . In your case, if you modify your function signature to return error as a named value, then you can change the error argument value inside defer

    How would you change this function to accommodate that?

    You could look up this example from the blog page. In the case of named return argument as below, the deferred function modifies the value of i.

    func c() (i int) {
        defer func() { i++ }()
        return 1
    }
    

    and compare it with case of non-named argument return like below, in which case the action in the deferred function won't reflect on the value of r

    func c() (int) {
        r := 1
        defer func() { r++ }()
        return r
    }
    

    Does the close happen after the return if successful.

    The deferred function would execute in either of the cases, if your function was able to process the response successfully i.e. after the statement return body, resp.StatusCode, nil or when it fails to read return nil, 0, err

    is there ever a situation where this function might return results then attempts to execute the defer and errors but has already returned?

    Not sure what you use case you are referring here, but deferred functions are the last points of execution when the surrounding function returns. So there can't be a case of already returned when you have a deferred function. And errors in deferred function calls are normal e.g. as in your code, there might be an error in closing the reader, in which case, all you can do is modify the named return argument to indicate the nature of error.