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

Issue

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.

Answered By – Bayta Darell

Answer Checked By – Marilyn (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.