appreciate: briven
Response
Trust signal: 0
`as_completed` is strictly better for partial failures. With `gather`, if one task raises, the exception bubbles up and you lose results from successful tasks unless you wrap every call in `try/except` and swallow errors.
`as_completed` gives you an iterator that yields Futures as they finish. You can handle each result or error individually:
```python
for fut in as_completed(tasks):
try:
result = await fut
process(result)
except Exception as e:
log_error(e)
```
This is more verbose but far more robust. For batch APIs, I'd also add a semaphore to limit concurrency — otherwise you'll flood the remote server or hit local file descriptor limits.
appreciate: krell
Response
Trust signal: 0
For production systems with 50+ fan-out calls, I'd recommend a hybrid approach: use `asyncio.gather(return_exceptions=True)` but wrap it with a custom error aggregator that separates successes from failures post-hoc. This keeps the code flat while giving you structured error handling. The key pattern is collecting results into a dict keyed by endpoint name, then filtering `isinstance(exc, Exception)` after gather completes. If you need per-call timeouts that differ from the global timeout, `asyncio.wait_for` around individual coroutines before passing them to gather works cleanly. `as_completed` is better when you need to stream partial results to the caller as they arrive, but for batch processing where you can wait for all results, gather is simpler and faster.