# ADR-0005: Nested Async Function Handling

## Status

Accepted (v0.2.6 bug fix)

## Context

Python coroutines and Tasks are different types:

```python
# Coroutine
async def my_func():
    return 42

coro = my_func()  # <coroutine object>
result = await coro  # 42

# Task
task = asyncio.create_task(my_func())  # <Task>
result = await task  # 42
```

When checking return values in async functions, it's easy to confuse:

```python
async def check():
    return True

# WRONG: Returns coroutine, not bool
if check():  # Always truthy (coroutine is non-None)
    ...

# CORRECT: Await first
result = await check()
if result:
    ...
```

## Decision

Always handle async return values correctly:

```python
# CORRECT: Await before checking
async def _execute_script(self, script: str) -> bool:
    try:
        result = await self._send_cdp(...)  # Await the coroutine
        return bool(result)  # Then check
    except Exception:
        return False

# Use the result
success = await self._execute_script("console.log('test')")
if success:
    print("Script executed")
```

## Consequences

### Positive

- **Correct boolean checks** — `bool(Task)` always `True`, `bool(await Task)` checks result
- **Prevents silent bugs** — Wrong check returns `True` instead of actual result

### Negative

- **Verbosity** — Need intermediate variable for checks
- **Easy mistake** — Forgetting `await` is a common Python async error

## Examples

### Before (v0.2.5 — Wrong)

```python
# BROKEN in v0.2.5
async def _execute_script(self, script: str) -> bool:
    try:
        # Returns coroutine, but we don't await
        return self._send_cdp(...)  # Returns Task <--- WRONG
    except Exception:
        return False

# Usage
if self._execute_script("..."):  # Always True (coroutine is truthy)
    print("This always runs")
```

### After (v0.2.6 — Correct)

```python
# FIXED in v0.2.6
async def _execute_script(self, script: str) -> bool:
    try:
        result = await self._send_cdp(...)  # Await first
        return bool(result)  # Then return
    except Exception:
        return False

# Usage
success = await self._execute_script("...")  # Await the result
if success:  # Check the boolean
    print("Script executed successfully")
```

### Common Patterns

```python
# Pattern 1: Await before check
result = await some_async_func()
if result:
    handle_success(result)

# Pattern 2: Compare to expected
result = await fetch_data()
if result is None:
    handle_not_found()

# Pattern 3: Boolean conversion
result = await check_condition()
return bool(result)  # Explicit conversion

# Pattern 4: Direct return (if not checking)
async def get_value():
    return await fetch_value()  # Propagates coroutine result
```

### Anti-Pattern: Checking Coroutine

```python
async def is_connected():
    return self._conn is not None

# WRONG
if is_connected():  # <coroutine> is truthy, always True
    do_something()

# CORRECT
if await is_connected():  # Awaits, gets bool
    do_something()
```

## References

- [Python Asyncio Documentation](https://docs.python.org/3/library/asyncio.html)
- [PEP 492 — Coroutines with async and await syntax](https://peps.python.org/pep-0492/)
- [Changelog — v0.2.6](../changelog.md)