back

by danabramov·13y ago·view on hn ↗
In some scenarios it simplifies the code greatly. It may or may not be applicable to your app.

Just a few weeks ago I was able to convert a nightmare-ish recursive asynchronous method to a `foreach` loop with `await`s inside.

It's cool when you can do this:

    var providerExceptions = new List<Exception> ();

    // Try each provider in turn
    foreach (var pi in providers) {
        token.ThrowIfCancellationRequested ();

        try {
            return await GetSession (provider, isLast, options, token);
        } catch (TaskCanceledException) {
            throw;
        } catch (Exception ex) {
            providerExceptions.Add (ex);
            // Fall back to next provider
        }
    }

    // Neither provider worked
    throw new AggregateException ("Could not obtain session via either provider", providerExceptions);

Or this:

    async Task<Session> GetSession (AccountProvider provider, bool isLast, LoginOptions options, CancellationToken token)
    {
        if (!SessionManager.NetworkMonitor.IsNetworkAvailable)
            throw new OfflineException ();

        var account = await GetAccount (provider, !isLast, options);
        if (account == null)
            throw new Exception ("The user chose to skip this provider.");

        var service = provider.Service;
        var session = new Session (service, account);

        if (service.SupportsVerification) {
            // For services that support verification, do it now
            try {
                await service.VerifyAsync (account, token);
            } catch (TaskCanceledException) {
                throw;
            } catch (Exception ex) {
                throw new InvalidOperationException ("Account verification failed.", ex);
            }
        }

        return session;
    }
Depending on the conditions, the method may or may not “freeze”, but the calling code doesn't care.
1 comments
In your first code sample, I'm pretty sure you don't need the return await GetSession (provider, isLast, options, token);

Unless there's more to the method, just remove the async modifier and directly return the task returned by GetSession.

If I did that, the code would always return at the first GetSession call.

Instead, it unwraps exceptions, and while it propagates cancelations, it ignores other exceptions and tries other providers in turn.

In fact, I could even use `break` or `continue` in the midst of async code, and there would be no problem.

Also I just love using `try` and `finally` in async code—with callbacks, you have to do finalization from all error and success paths (which can be a lot of places).

await will unwrap exceptions from the task object
Exactly, and thus I'll be able to try next provider in the loop.