2

In other languages, I can run several tasks in concurrently and get the result of each in corresponding variable.

For example in JS:

getApi1()
 .then( res => console.debug("Result 1: ", res) )

getApi2()
 .then( res => console.debug("Result 2: ", res) )

getApi3()
 .then( res => console.debug("Result 3: ", res) )

And I know exactly in which variable the result of the execution of which function.

The same is in Python asyncio:

task1 = asyncio.create_task(getApi1)
task2 = asyncio.create_task(getApi2)
result1 = await task1
result2 = await task2

I'm new in Go lang. All guides say to use channels with goroutines.

But I don't understand, when I read from the channel, how to be sure which message matches which result?

resultsChan := make(chan map)

go getApi1(resultsChan)
go getApi2(resultsChan)
go getApi3(resultsChan)

for {
 result, ok := <- resultsChan

 if ok == false {
  break
 
 } else {

  // HERE
  fmt.Println(result)  // How to understand which message the result of what API request?
 
 }

}

morfair
  • 518
  • 1
  • 6
  • 17
  • 1
    `make(chan map)` wouldn't compile. Please post a MWE. To answer your question, if you care about where the result comes from, you should use an appropriate channel element type (e.g. `type Result struct { api string }`). – jub0bs Jun 03 '21 at 08:29
  • @jub0bs, any way, how to match results to my API calls? – morfair Jun 03 '21 at 08:31
  • goroutines don't return *results:* they run until they return, but their return type is always "returns nothing at all". So there is no result to *get*. – torek Jun 03 '21 at 09:15
  • Does this answer your question? [Catching return values from goroutines](https://stackoverflow.com/questions/20945069/catching-return-values-from-goroutines) – torek Jun 03 '21 at 09:20
  • What is the element type of `resultsChan`? If it's a struct, could you not simply add a field to it that would indicate where the result comes from? – jub0bs Jun 04 '21 at 14:41

4 Answers4

2

How to understand which message the result of what API request?

If the same channel is to communicate the results from all your getApiN functions to main and you want to programmatically determine where each result came from, you can simply add a dedicated field to your channel element type. Below, I've declared a custom struct type named Result with a field named orig for precisely that purpose.

package main

import (
    "fmt"
    "sync"
)

type Origin int

const (
    Unknown Origin = iota
    API1
    API2
    API3
)

type Result struct {
    orig Origin
    data string
}

func getApi1(c chan Result) {
    res := Result{
        orig: API1,
        data: "some value",
    }
    c <- res
}

func getApi2(c chan Result) {
    res := Result{
        orig: API2,
        data: "some value",
    }
    c <- res
}

func getApi3(c chan Result) {
    res := Result{
        orig: API3,
        data: "some value",
    }
    c <- res
}

func main() {
    results := make(chan Result, 3)
    var wg sync.WaitGroup
    wg.Add(3)
    go func() {
        defer wg.Done()
        getApi1(results)
    }()
    go func() {
        defer wg.Done()
        getApi2(results)
    }()
    go func() {
        defer wg.Done()
        getApi3(results)
    }()
    go func() {
        wg.Wait()
        close(results)
    }()
    for res := range results {
        fmt.Printf("%#v\n", res)
    }
}

(Playground)

Possible output (the order of results isn't deterministic):

main.Result{orig:1, data:"some value"}
main.Result{orig:2, data:"some value"}
main.Result{orig:3, data:"some value"}

At any rate, I would not follow wic's suggestion; your problem is simply not a good use case for reflection. As Rob Pike puts it,

Reflection is never clear. Another thing you see on Stack Overflow a lot is people trying to use reflect and wondering why it doesn't work. It doesn't work because it's not for you... Very, very few people should be playing with reflection. It's a very powerful but very difficult-to-use feature. [...]

jub0bs
  • 60,866
  • 25
  • 183
  • 186
0

You could use chebyrash/promise for that (which uses channel behind the scene)

var p1 = promise.Resolve(123)
var p2 = promise.Resolve("Hello, World")
var p3 = promise.Resolve([]string{"one", "two", "three"})

results, _ := promise.All(p1, p2, p3).Await()
fmt.Println(results)
// [123 Hello, World [one two three]]

As explained in "Catching return values from goroutines":

It is a design choice by Go creators.
There's a whole lot of abstractions/APIs to represent the value of async I/O operations - promise, future, async/await, callback, observable, etc

The project I mention above is an example of composition which allows to get "promises".


To achieve the same natively, you would need one channel per expected result.
See as an example "Use Go Channels as Promises and Async/Await" (from Minh-Phuc Tran), and this playground example:

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func longRunningTask() <-chan int32 {
    r := make(chan int32)

    go func() {
        defer close(r)
        
        // Simulate a workload.
        time.Sleep(time.Second * 3)
        r <- rand.Int31n(100)
    }()

    return r
}

func main() {
    aCh, bCh, cCh := longRunningTask(), longRunningTask(), longRunningTask()
    a, b, c := <-aCh, <-bCh, <-cCh
    
    fmt.Println(a, b, c)
}

Interesting comments from the OP:

What if I need run 500 API calls concurrently? Should I make 500 channels?

See "Max number of goroutines": 500 goroutines (and their channels) is nothing

What if I don't know in advance count of API calls? For example, I get params and get API calls over it array unknown length?

Then, assuming we are talking about a large number of calls, you can use a worker pool, as in this more sophisticated example.
You would need to return a result tagged with a job id in order to match it with the variable which expect a specific result.

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • This is interesting, thanks. But I want to know how to do it natively. – morfair Jun 03 '21 at 08:40
  • @morfair Sure. I have edited the answer to illustrate a (simple) native implementation. – VonC Jun 03 '21 at 08:46
  • No, no, no... What if I need run 500 API calls concurrently? Should I make 500 channels? What if I don't know in advance count of API calls? For example, I get params and get API calls over it array unknown length? – morfair Jun 03 '21 at 08:48
  • @morfair 1/ They are cheap, so you can have a lot of them. 2/ It depends on the nature of your program: you can have for example a pool of channels in order to mange *large* batch of calls (and by large, I mean way bigger than 500). – VonC Jun 03 '21 at 08:50
  • And again, how to match results from channels pool?.. – morfair Jun 03 '21 at 08:52
  • @morfair you don't have to match results: each variable in a given batch is assigned *one* channel which will bring back its assigned result. – VonC Jun 03 '21 at 08:53
0

An other way to do is to use goroutine rather than channels. It depends whether you want to have some synchronization on the callbacks or not. This:

getApi1()
 .then( res => console.debug("Result 1: ", res))

getApi2()
 .then( res => console.debug("Result 2: ", res) )

getApi3()
 .then( res => console.debug("Result 3: ", res) )

Would become

go func(){
    res := getApi1()
    fmt.Println("Result 1: ", res)
}
go func(){
    res := getApi2()
    fmt.Println("Result 2: ", res)
}
go func(){
    res := getApi3()
    fmt.Println("Result 3: ", res)
}
aureliar
  • 1,476
  • 4
  • 16
  • I would like both. First, I should get all results and hanle its together. Secont, I must be able to process separately. – morfair Jun 03 '21 at 09:27
-1

The best practice is to have a channel for each goroutine and make a select statement containing all channels, but it sounds like much-repeated code. So, you need to do that but with a generic code, Go have a powerful package called reflection. If you are new to Go, I recommend you to learn reflection. So, you can apply this answer https://stackoverflow.com/a/19992525/10446155 and the code looks like this:

package main

import (
    "fmt"
    "reflect"
)

func getApi1(c chan string) {
    c <- "value from Api1"
}

func getApi2(c chan string) {
    c <- "value from Api2"
}

func getApi3(c chan string) {
    c <- "value from Api3"
}

// Array with all channels
var responses []chan string

func init() {
    responses = make([]chan string, 3)
    for i := 0; i < 3; i++ {
        responses[i] = make(chan string)
    }
}

func main() {
    // Call each ApiFunction with different channel
    go getApi1(responses[0])
    go getApi2(responses[1])
    go getApi3(responses[2])

    // Generic select statement with reflection
    cases := make([]reflect.SelectCase, len(responses))
    for i, ch := range responses {
        cases[i] = reflect.SelectCase{
            Dir:  reflect.SelectRecv,
            Chan: reflect.ValueOf(ch),
        }
    }

    count := len(cases)
    for {
        chosen, value, ok := reflect.Select(cases)
        if !ok {
            panic(fmt.Sprintf("channel at index %d is closed", chosen))
        }
        fmt.Printf("getApi%d returns: %s\n", chosen+1, value.String())
        // Count each select, and break if all are made. If you don't 
        // do this, then your code enter in a deadlock condition
        count -= 1
        if count == 0 {
            break
        }
    }
}

Note: I suppose all ApiFunctionrs returns a string

wic
  • 362
  • 3
  • 14