I am using core async. In the scenario I care for, I am using it similar to how one would use async / await (all processes are kicked off by a caller, and eventually return)
Problem: Propagating errors
By default, go-blocks fail silently when there are errors
(defn go-uh-oh
[]
(go
(throw (Exception. "uh-oh"))))
(<!! (go
(let [a-chan (go-uh-oh item-one)
b-chan (go-uh-oh item-two)]
[(<! a-chan) (<! b-chan)]))
The top-level take would simply return [nil nil]. Instead, it would be great if we propagated errors and threw.
Potential Solution: go-try
To solve this, it looks like the idiomatic solution is to introduce the go-try and <? macro. The go-try macro wraps the body of a go block in a try-catch, and returns the error if it exists. <? then takes from these kind of go-blocks. If the item is an error, it re-throws. This way errors propagate. Here's an example implementation: https://github.com/alexanderkiel/async-error
So if we wrote the above like so, errors propagate:
(defn go-uh-oh
[arg]
(go-try
(throw (Exception. "uh-oh"))))
(<??
(go-try (let [a-chan (go-uh-oh 1)
b-chan (go-uh-oh 2)]
[(<? a-chan) (<? b-chan)])))
Errors would propagate and we would get the result.
Buut, problem with go-try
But there's a problem with using go-try and <?. Now, if I have some intermediate functions to compose some go-try blocks, errors would get lost again. For example:
(<?? (a/into [] (a/merge [(uh-oh-go-try) (uh-oh-go-try)])))
Here, <?? would see a vector of [Error, Error], and would not propagate anything up.
I could change the way <?? is implemented, and have it consider arrays of errors too. But I wonder if there is a better way. How do you handle error propagation in this case?