Future / promise in golang

Time:2021-2-23

Nowadays, the most common bottleneck in application execution is network request. The network request takes only a few milliseconds, but it takes a hundred times longer to wait for the return. Therefore, if you execute multiple network requests in parallel, it is the best choice to reduce the delay.Future/PromiseIs one of the means to achieve this goal.

A future means that “in the future” you need something (usually the result of a network request), but you have to initiate such a request now, and the request will be executed asynchronously. Or to put it another way, you need to execute an asynchronous request in the background.

The future / promise pattern is implemented in many languages. For example, es2015 has promise and async await, Scala has built-in future, and finally goroutine and channel in golang can achieve similar functions. Here is a simple implementation.

//RequestFuture, http request promise.
func RequestFuture(url string) <-chan []byte {
    c := make(chan []byte, 1)
    go func() {
        var body []byte
        defer func() {
            c <- body
        }()

        res, err := http.Get(url)
        if err != nil {
            return
        }
        defer res.Body.Close()

        body, _ = ioutil.ReadAll(res.Body)
    }()

    return c
}

func main() {
  future := RequestFuture("https://api.github.com/users/octocat/orgs")
  body := <-future
  log.Printf("reponse length: %d", len(body))
}

RequestFutureMethods the science department returns a channel. At this time, the HTTP request is running asynchronously in a goroutine background. The main method can continue to execute other code, such as triggering other codeFutureAnd so on. When the result is needed, we need to read the result from the channel. If the HTTP request has not returned, the current goroutine will be blocked until the result is returned.

However, the above method has one limitation. Error cannot be returned. In the above example, if there is an error in the HTTP request, the value of body will be nil / empty. However, since channel can only return one value, you need to create a separate struct to wrap the two returned results.

Results after modification:

// RequestFutureV2 return value and error
func RequestFutureV2(url string) func() ([]byte, error) {
    var body []byte
    var err error

    c := make(chan struct{}, 1)
    go func() {
        defer close(c)

        var res *http.Response
        res, err = http.Get(url)
        if err != nil {
            return
        }

        defer res.Body.Close()
        body, err = ioutil.ReadAll(res.Body)
    }()

    return func() ([]byte, error) {
        <-c
        return body, err
    }
}

This method returns two results, which solves the limitation of the first method. This is how it is used:

func main() {
    futureV2 := RequestFutureV2("https://api.github.com/users/octocat/orgs")

    // not block
    log.Printf("V2 is this locked again")

    bodyV2, err := futureV2() // block
    if err == nil {
        log.Printf("V2 response length %d\n", len(bodyV2))
    } else {
        log.Printf("V2 error is %v\n", err)
    }
}

The benefit of the above changes is thatfutureV2()Method can be called multiple times. And can return the same result.

However, if you want to implement many different asynchronous functions in this way, you need to write a lot of extra code. We can write a util method to overcome this difficulty.

// Future boilerplate method
func Future(f func() (interface{}, error)) func() (interface{}, error) {
    var result interface{}
    var err error

    c := make(chan struct{}, 1)
    go func() {
        defer close(c)
        result, err = f()
    }()

    return func() (interface{}, error) {
        <-c
        return result, err
    }
}

callFutureWhen using the method, I will perform many channel skills in the room. In order to achieve the purpose of universal, there is a way from[]buyte->interface{}->[]byteType conversion for. If there is an error, a runtime error is raisedpanic

Original text:http://labs.strava.com/blog/f…

Stay tuned to my next episode