const [[rA, errA], [rB, errB]] = await Promise.all([
engine.doSomethingWithA(),
engine.doSomethingWithB()
])
At least these days you can ask an LLM to write the WaitGroup boilerplate for you in Go. const [[rA, errA], [rB, errB]] = await Promise.all([
engine.doSomethingWithA(),
engine.doSomethingWithB()
])
At least these days you can ask an LLM to write the WaitGroup boilerplate for you in Go.Not to mention waitgroups are way overkill for this. You’d just use a channel or two. Or an errgroup if you want to be fancy.
type result[T any] struct {
el T
err error
}
chanA := make(chan result[aResultType])
chanB := make(chan result[bResultType])
go func() {
defer close(chanA)
a := &blah{}
rA, err := engine.doSomethingWithA()
chanA <- result[aResultType]{
el: rA,
err: err,
}
}()
go func() {
defer close(chanB)
b := &bloop{}
rB, err := engine.doSomethingWithB()
chanB <- result[bResultType]{
el: rB,
err: err,
}
}
resultA := <- chanA
resultB := <- chanB func runTask[T any](task func() (T, error)) chan result[T] {
ch := make(chan result[T])
go func() {
defer close(ch)
res, err := task()
ch <- result[T]{ el:res, err:err } }()
return ch }
Does that sort of thing happen much in practice?One of the common pasttimes in the threaded versus async debate is to present code in which one side uses all sorts of helpers and patterns and libraries and the other side is presented through writing it "raw". The great-grandparent of my post here is guilty of this. While there are interesting reasons to debate threaded versus async code, this is not one of them. Both of them are absolutely capable of writing the moral equivalent of "output = parallel_map(myMapFunc, input)" and all similar operations to within practical epsilon of each other, and anyone citing this sort of thing as an argument on either side should probably be ignored. And both languages will feature code written by people who don't know that, and it shouldn't count against either.
And I'm admitting this "against interest", as the lawyers say. I'm not striking a disinterested "middle of the road" pose here. I'm hugely on the side of threads. But it is still not a relevant criticism of async. You can easily "parallel map" with either abstraction and you are not stuck unable to abstract the control flow in either case.
^Without any non-local changes, obviously.
And this is a bit of a weird case, at least where I am. I tend to have a bunch of things to process and have one goroutine sending keys/indices/etc to a channel that multiple workers are processing off of.
We did have an abstraction for that at one point, but there were enough edge cases in our domain that we either had to develop a config system or rip it out and go back to writing each one (we went with the latter after an attempt at the former went really bad).
If A failed, the whole function is failed, and we don't need B any more. To save resources we should cancel B. And vice versa, cancel A if B failed.
import github.com/carlmjohnson/flowmatic
func whatever(ctx context.Context) {
err := flowmatic.Race(ctx, taskA, taskB)
}
Provide your definition of taskA and taskB, of course.As I said in another message, this is not a particularly fruitful line of attack in either direction. All the languages in question are perfectly capable of abstractions.
I guess I'm just still salty when someone commented (in another post a long time ago) that golang only has `go` (compared to `launch`, `async`, and `coroutineScope` in Kotlin) and is simpler.
> All the languages in question are perfectly capable of abstractions.
I don't think async functions in JS can be cancelled though.
Me neither, however, it is generally the most flexible approach and I can see why a library takes it. If you want to communicate it via the return, you also have to impose a restriction that the tasks all return the same type. I think it makes sense for a library to work this way because you can easily add this around the library call yourself, but it's more difficult to go the other way. (Not impossible, just more difficult.)
"I don't think async functions in JS can be cancelled though."
Poking around, it looks pretty hard.
That said, cancelling in generally is very difficult in imperative languages, so even as someone who finds async in JS quite distasteful I can't dock too many points. Go basically just reifies the JS solution into a standard community practice, which is definitely an improvement since you can largely rely on it being supported everywhere, but one could reasonably debate how good it is. It is occasionally a problem that if you want to cancel an ongoing computation you may have to have your code manually check a context every so often, because there's no "real" connection between a context and a goroutine.
https://developer.mozilla.org/en-US/docs/Web/API/AbortContro...