Search code examples
asynchronousgogoroutine

What is the idiomatic way of dealing with return data from a conditionally asynchronous function?


I have a function which may or may not be called as an asynchronous go-routine.

func APICall(request *HTTPRequest) *HTTPResponse

*HTTPRequest is a pointer to a struct which contains various pieces of data required in order to build a request:

type HTTPRequest struct {
    // Represents a request to the twitter API
    method string
    baseurl string
    urlParams map[string]string
    bodyParams map[string]string
    authParams map[string]string
    responseChan chan *HTTPResponse
}

If called as a goroutine, i.e a channel is passed in; we build the request and write the response into the *HTTPResponse object (also a struct) of the provided channel. What is the most graceful / idiomatic way to accept a call to the function without a channel (ie. Not async)

At the moment, we do something like this within the body of APICall to deal with both kinds of function call:

if request.responseChan != nil { // If a response channel has been specified, write to that channel
request.responseChan <- &twitterHTTPResponse{body, nil}
return nil // Not returning a struct
} else {
return &twitterHTTPResponse{body, nil} // Return a pointer to a new struct representing the response
}

Are we along the right lines?


Solution

  • The idiomatic approach is to provide a synchronous API:

    type HTTPRequest struct {
        // Represents a request to the twitter API
        method string
        baseurl string
        urlParams map[string]string
        bodyParams map[string]string
        authParams map[string]string
    }
    
    func APICall(request *HTTPRequest) *HTTPResponse {
        ...
        return &twitterHTTPResponse{body, nil} 
    }
    

    The caller an can easily create a goroutine if it needs to run the call concurrently. For example:

    r := make(chan *HTTPResponse) 
    go func() {
        r <- APICall(req)
    }()
    
    ... do some other work
    
    resp := <- r
    

    Synchronous APIs are idiomatic for a couple of reasons:

    • Synchronous APIs are easier to use and understand.
    • Synchronous APIs don't make incorrect assumptions about how the application is managing concurrency. For example, the application may want to use a wait group to wait for completion instead of receiving on a channel as assumed by the API.