Error handling with async throws

I am converting my code from completionHandlers to async / await. During this process, I am overthinking my error-handling and I am not sure, what way would be better. In the end, I think both ways will work, but both have their advantages and disadvantages. I would just like so hear some opinions and maybe I oversee another solution.

So I have an async function that is throwable and called from many places in my program. This function does some work and then, if there is no error, returns a value. Since I wrote the function, I know what errors it could throw. Let's make a simple example:

func calledFunction(value: String) async throws -> String {
	// do some work
	if condition {
		throw MyError.someError
	}
	return "value"
}

In this case I have to call this function every time with do { ... try and every time I have to care about the error handling:

func callerFunction1() async {
	do {
		let result = try await calledFunction(value: ...)
		// do some further work with the result
	} catch let error {
		switch error {
		case MyError.someError:
			// ...
		// ... handle more cases
		default:
			// ...
	}
}
func callerFunction2() async {
	do {
		let result = try await calledFunction(value: ...)
		// do some further work with the result
	} catch let error {
		switch error {
		case MyError.someError:
			// ...
		// ... handle more cases
		default:
			// ...
	}
}

Since I know, what errors are able in the called function, I would have to do always the same error handling as just showed.

Of course, I could outsource the error handling to another function like this and provide some more informations for the error handling:

func callerFunction1() async {
	do {
		let result = try await calledFunction(value: ...)
		// do some further work with the result
	} catch let error {
		self.handleError(error: error, moreInfo: "Calling from func1")
	}
}
func callerFunction2() async {
	do {
		let result = try await calledFunction(value: ...)
		// do some further work with the result
	} catch let error {
		self.handleError(error: error, moreInfo: "Calling from func2")
	}
}

func handleError(error: Error, moreInfo: String) {
	switch error {
		// .... here we can use the moreInfo so that we can show the
		// user, in which function the error occured
	}
}

This way has the disadvantage, that I must call the error handling every time.

So another approach would be to do the error handling in the called function (I then can also provide additional informations from the calling function to make the error more informative). But If I go this way, the called function would no longer be throwable since I handle all the errors. At least, the return value would then be optional. It would look like this:

func calledFunction(value: String, moreInfo: String) async -> String? {
	// do some work
	if condition {
		// handle error 1 with moreInfo
	} else if condition2 {
		// handle error 2 with moreInfo
	} else {
		return "value"
	}
	return nil
}

func callerFunction1() async {
	if let result = await calledFunction(value: ..., moreInfo: "Calling from func1") {
		// do some further work with the result
	}
}
func callerFunction2() async {
	if let result = await calledFunction(value: ..., moreInfo: "Calling from func2") {
		// do some further work with the result
	}
}

What do you think, what way would you prefer?

I'm curious about this too :eyes:

What I usually do is to either wrap the call in a do { } catch { } if I care about the error OR try? await functionCall() if I do not care about the error.