Error handling in async code

try/except around await works exactly as you'd expect

Nothing new is needed here — try/except from the Python setup lesson applies to await-ed calls exactly the same way it applies to a regular function call:

Try it — edit and run

The exception propagates out of call_calculator_api(), through the await, and is caught exactly where you’d expect — await doesn’t change how exceptions travel, it just marks where the pause happens.

gather()'s default behavior: one failure stops the whole thing

asyncio.gather() needs its own explanation, though, since it’s combining multiple coroutines at once. By default, if any one of them raises, gather() itself raises that same exception — even though the other coroutines might have succeeded, or might still be running:

Try it — edit and run

The whole gather() call raises, results never gets assigned, and call_search_api()’s successful "3 results found" is simply discarded — even though that call genuinely did succeed. This matters because it’s easy to assume gather() behaves like a list of independent attempts, each one reported on its own — by default, it doesn’t; one failure looks, from the caller’s side, exactly like the whole batch failed.

return_exceptions=True — collect failures instead of raising

Passing return_exceptions=True changes this: instead of raising on the first failure, gather() waits for every coroutine to finish (success or failure) and returns a list where each position is either that coroutine’s actual result, or the exception it raised:

Try it — edit and run

Both positions are filled — call_search_api()’s real result at index 0, and the actual ValueError object (not raised — just sitting there as a value) at index 1, in the same order the coroutines were passed in, exactly as gather()’s ordering already worked without return_exceptions. Distinguishing a real result from a failure means checking each item’s type:

Try it — edit and run

isinstance(result, Exception) works here because every built-in exception — and every custom exception you’d define yourself — is a subclass of Exception, the same isinstance/inheritance relationship covered back in the OOP lesson, applying here to tell an exception object apart from a normal return value sitting in the same list.

Check your understanding
1/5

Does wrapping await some_coroutine() in a try/except work the same way it would for a regular function call?

Exercise · Graded

Use asyncio.gather(..., return_exceptions=True) to fetch every id concurrently, then split the results into two lists — successful result strings, and str(e) for each exception — preserving each item's relative order within its own list.