in async code ,errors belong to the task ,not the caller.
in sync code ,the caller owns the stack ,so it makes sense they own the error. but async splits that. now each async function runs like a background job. that job should handle its own failure =retry ,fallback ,log because the caller usually cant do much anyway.
write async blocks like isolated tasks. contain errors inside unless the caller has a real decision to make. global error handler picks up the rest
One of the things I love most about Elixir is that it makes asynchronous error handling easier than any other language I've used. Asynchronous code used to be the source of many difficult bugs in the teams I've worked with, but Elixir's (or, more accurately, Erlang's) "let it crash" architecture helps eliminate many of these issues.
Exceptions get a lot of hate, but of the three styles, I keep coming back to exceptions. Ages ago I built an application with error codes, and went back to exceptions, because I thought the ceremony of error checking was not worth it. On occasion, I'll use a get-last-error style, particularly when the error is something the user is intended to address. But for most of my applications (which are usually not libraries and are code under my control) I like exceptions.
I always have global error handler that logs and alerts of anything uncaught. This allows me to code the happy path. Most of the time, it's not worth figuring out how to continuing processing under every possible error, so to fail and bail is my default approach. If I later determine that its something that can be handled to continue processing, then I update that code path to handle that case.
Most of my code is web applications, so that is where I'm coming from.