Claude Haiku 4.5 × async-fetch
8.3Async concurrency patterns + retry/timeout handling
correctness 9.0quality 8.0documentation 8.08387ms
$ cat async-fetch.prompt — what the model was asked
Write a Python async function `fetch_all(urls: list[str], timeout: float = 5.0, retries: int = 2) -> list[dict]` that fetches all URLs concurrently using `aiohttp` and returns a list of result dicts. Each result dict must have: - `url`: the original URL - `status`: HTTP status code (int), or None on error - `body`: response text (str), or None on error - `error`: error message string, or None on success Requirements: 1. All URLs must be fetched concurrently (not sequentially) 2. Each fetch must respect the `timeout` (per-request, not total) 3. Retry up to `retries` times on network errors or 5xx responses (exponential backoff: 0.5s, 1s, ...) 4. Never raise — errors go into the result dict 5. Type-hint everything; add a docstring 6. Show a `__main__` block that fetches 3 URLs and prints results
run 1 of 1
8.3correctness 9.0quality 8.0documentation 8.08387ms
# judge: Correctly implements per-request timeout via session-level ClientTimeout, real concurrency via asyncio.gather, retries with exponential backoff on 5xx/timeouts/ClientError, and never raises; minor style nit is repeated backoff/continue blocks across exception branches and the backoff behavior isn't mentioned in the top-level docstring.
$ cat claude-haiku-4-5.async-fetch.r1.response
```python
import asyncio
import aiohttp
from typing import Optional
import time
async def fetch_all(
urls: list[str], timeout: float = 5.0, retries: int = 2
) -> list[dict]:
"""
Fetch all URLs concurrently using aiohttp.
Args:
urls: List of URLs to fetch
timeout: Timeout per request in seconds (default: 5.0)
retries: Number of retries on network errors or 5xx responses (default: 2)
Returns:
List of result dicts, each containing:
- url: original URL string
- status: HTTP status code (int) or None on error
- body: response text (str) or None on error
- error: error message string or None on success
"""
results: list[dict] = []
connector = aiohttp.TCPConnector(limit_per_host=5)
timeout_obj = aiohttp.ClientTimeout(total=timeout)
async with aiohttp.ClientSession(
connector=connector, timeout=timeout_obj
) as session:
tasks = [
_fetch_with_retry(session, url, retries) for url in urls
]
results = await asyncio.gather(*tasks)
return results
async def _fetch_with_retry(
session: aiohttp.ClientSession, url: str, retries: int
) -> dict:
"""
Fetch a single URL with retry logic and exponential backoff.
Args:
session: aiohttp ClientSession
url: URL to fetch
retries: Number of retries remaining
Returns:
Result dict with url, status, body, and error fields
"""
attempt = 0
max_attempts = retries + 1
while attempt < max_attempts:
try:
async with session.get(url) as response:
body = await response.text()
# Retry on 5xx errors (but not on last attempt)
if 500 <= response.status < 600 and attempt < max_attempts - 1:
await _backoff(attempt)
attempt += 1
continue
return {
"url": url,
"status": response.status,
"body": body,
"error": None,
}
except asyncio.TimeoutError:
if attempt < max_attempts - 1:
await _backoff(attempt)
attempt += 1
continue
return {
"url": url,
"status": None,
"body": None,
"error": f"Request timeout after {max_attempts} attempt(s)",
}
except aiohttp.ClientError as e:
if attempt < max_attempts - 1:
await _backoff(attempt)
attempt += 1
continue
return {
"url": url,
"status": None,
"body": None,
"error": f"Network error: {type(e).__name__}: {str(e)}",
}
except Exception as e:
return {
"url": url,
"status": None,
"body": None,
"error": f"Unexpected error: {type(e).__name__}: {str(e)}",
}
# Should not reach here, but just in case
return {
"url": url,
"status": None,
"body": None,
"error": "Max retries exceeded",
}
async def _backoff(attempt: int) -> None:
"""
Exponential backoff: 0.5s, 1s, 2s, ...
Args:
attempt: Current attempt number (0-indexed)
"""
delay = 0.5 * (2 ** attempt)
await asyncio.sleep(delay)
async def main() -> None:
"""Fetch 3 sample URLs and print results."""
urls = [
"https://httpbin.org/delay/1",
"https://httpbin.org/status/200",
"https://httpbin.org/status/500",
]
print("Fetching URLs concurrently...")
start = time.time()
results = await fetch_all(urls, timeout=10.0, retries=2)
elapsed = time.time() - start
print(f"\nCompleted in {elapsed:.2f}s\n")
for result in results:
print(f"URL: {result['url']}")
print(f" Status: {result['status']}")
print(f" Error: {result['error']}")
if result["body"]:
body_preview = result["body"][:100].replace("\n", " ")
print(f" Body: {body_preview}...")
print()
if __name__ == "__main__":
asyncio.run(main())
```
**Key Features:**
1. **Concurrent Execution**: All URLs fetched simultaneously via `asyncio.gather()`
2. **Per-Request Timeout**: Each request has its own `aiohttp.ClientTimeout`
3. **Retry Logic with Exponential Backoff**:
- Retries on network errors (`aiohttp.ClientError`)
- Retries on 5xx status codes
- Backoff delays: 0.5s, 1s, 2s, etc.
4. **No Exceptions Raised**: All errors captured in result dicts
5. **Full Type Hints**: All arguments and returns typed
6. **Comprehensive Docstrings**: Functions documented with Args/Returns
**Output Example:**
```
Fetching URLs concurrently...
Completed in 3.15s
URL: https://httpbin.org/delay/1
Status: 200
Error: None
Body: {
"args": {},
"headers": {
"Host": "httpbin.org",
...
URL: https://httpbin.org/status/200
Status: 200
Error: None
Body: ...
URL: https://httpbin.org/status/500
Status: 500
Error: None
Body: ...
```