catch() approaches to handle errors for asynchronous code. When returning a promise within a try block, make sure to await it if you want the try... catch block to catch the error. Be aware when wrapping errors and rethrowing, that you lose the stack trace with the origin of the error.
No. You don't need to use try/catch in every async/await. You only need to do it at the top level.
If await gets a non-promise object with . then , it calls that method providing the built-in functions resolve and reject as arguments (just as it does for a regular Promise executor). Then await waits until one of them is called (in the example above it happens in the line (*) ) and then proceeds with the result.
You can move the logic outside of the catch
block and rethrow the exception after, if needed, by using ExceptionDispatchInfo
.
static async Task f()
{
ExceptionDispatchInfo capturedException = null;
try
{
await TaskThatFails();
}
catch (MyException ex)
{
capturedException = ExceptionDispatchInfo.Capture(ex);
}
if (capturedException != null)
{
await ExceptionHandler();
capturedException.Throw();
}
}
This way, when the caller inspects the exception's StackTrace
property, it still records where inside TaskThatFails
it was thrown.
You should know that since C# 6.0, it's possible to use await
in catch
and finally
blocks, so you could in fact do this:
try
{
// Do something
}
catch (Exception ex)
{
await DoCleanupAsync();
throw;
}
The new C# 6.0 features, including the one I just mentioned are listed here or as a video here.
If you need to use async
error handlers, I'd recommend something like this:
Exception exception = null;
try
{
...
}
catch (Exception ex)
{
exception = ex;
}
if (exception != null)
{
...
}
The problem with synchronously blocking on async
code (regardless of what thread it's running on) is that you're synchronously blocking. In most scenarios, it's better to use await
.
Update: Since you need to rethrow, you can use ExceptionDispatchInfo
.
We extracted hvd's great answer to the following reusable utility class in our project:
public static class TryWithAwaitInCatch
{
public static async Task ExecuteAndHandleErrorAsync(Func<Task> actionAsync,
Func<Exception, Task<bool>> errorHandlerAsync)
{
ExceptionDispatchInfo capturedException = null;
try
{
await actionAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
capturedException = ExceptionDispatchInfo.Capture(ex);
}
if (capturedException != null)
{
bool needsThrow = await errorHandlerAsync(capturedException.SourceException).ConfigureAwait(false);
if (needsThrow)
{
capturedException.Throw();
}
}
}
}
One would use it as follows:
public async Task OnDoSomething()
{
await TryWithAwaitInCatch.ExecuteAndHandleErrorAsync(
async () => await DoSomethingAsync(),
async (ex) => { await ShowMessageAsync("Error: " + ex.Message); return false; }
);
}
Feel free to improve the naming, we kept it intentionally verbose. Note that there is no need to capture the context inside the wrapper as it is already captured in the call site, hence ConfigureAwait(false)
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With