There are many ways to make each iteration executed asynchronously. One of them is by taking advantage of goroutine and channel (like what you desired).
Please take a look at example below. I think it'll be easier if I put the explanations as comments on each part of the code.
// prepare the channel for data transporation purpose between goroutines and main routine
resChan := make(chan []interface{})
for ID, person := range people {
// dispatch an IIFE as goroutine, so no need to change the `someApiCall()`
go func(id string, person Person) {
result := someApiCall(person)
// send both id and result to channel.
// it'll be better if we construct new type based id and result, but in this example I'll use channel with []interface{} type
resChan <- []interface{}{id, result}
}(ID, person)
}
// close the channel since every data is sent.
close(resChan)
// prepare a variable to hold all results
results := make(map[string]Result)
// use `for` and `range` to retrieve data from channel
for res := range ch {
id := res[0].(string)
person := res[1].(Person)
// append it to the map
result[id] = person
}
// And do something with all the results once completed
Another way is by using few sync
API like sync.Mutex
and sync.WaitGroup
to achieve same target.
// prepare a variable to hold all results
results := make(map[string]Result)
// prepare a mutex object with purpose is to lock and unlock operations related to `results` variable, to avoid data race.
mtx := new(sync.Mutex)
// prepare a waitgroup object for effortlessly waits for goroutines to finish
wg := new(sync.WaitGroup)
// tell the waitgroup object how many goroutines that need to be finished
wg.Add(people)
for ID, person := range people {
// dispatch an IIFE as goroutine, so no need to change the `someApiCall()`
go func(id string, person Person) {
result := someApiCall(person)
// lock the append operation on `results` variable to avoid data race
mtx.Lock()
results[ID] = result
mtx.Unlock()
// tell waitgroup object that one goroutine is just finished
wg.Done()
}(ID, person)
}
// block the process synchronously till all goroutine finishes.
// after that it'll continue to next process underneath
wg.Wait()
// And do something with all the results once completed
A warning. Both approaches above are fine to use on a case that there are only few data that need to be iterated. If there are a lot of it, it'll not be good, there will be tons of goroutine dispatched nearly in the same time, and will cause very high machine memory usage. I suggest to take a look about worker pool technique to improve the code.