For simple things I would use a set of local variables and some goroutines that set those variables, along with a waitgroup to know when everything is finished:
var a string
var b string
wg := sync.WaitGroup{}
wg.Add(2)
go func(){
time.Sleep(5 * time.Second) // make a request
a = "foo"
wg.Done()
}()
go func(){
time.Sleep(3 * time.Second) // make a request
b = "bar"
wg.Done()
}()
wg.Wait()
fmt.Println(a,b) //combine results
playground link
If you want more complicated behaviour like timeouts or partial results, then you probably want your subrequests to communicate results back on a channel you can select on:
// make sure to buffer to max number of senders so garbage collection can clean up
// if we time out
ch := make(chan string, 2)
go func() {
time.Sleep(5 * time.Second) // make a request
ch <- "foo"
}()
go func() {
time.Sleep(2 * time.Second) // make a request
ch <- "bar"
}()
results := []string{}
timeout := time.After(4 * time.Second)
Loop:
for {
select {
case r := <-ch:
results = append(results, r)
if len(results) == 2 {
break Loop
}
case <-timeout:
break Loop
}
}
fmt.Println(results)
playground Link
That doesn't fully preserve order, but you could make another channel if that is important. Thats the general idea anyway.