AsyncMaybe Monad
Async optional values with awaitable operations
The AsyncMaybe monad extends Maybe with async operations, supporting awaitable computations for optional values. It combines the benefits of async/await with the safety of optional value handling.
AsyncMaybe[T] wraps a Maybe[T] and provides async operations. All operations that might be async are awaitable, and it converts to/from regular Maybe.
Creating AsyncMaybes
Creating with Values
The AsyncMaybe.some method creates an AsyncMaybe containing a value.
from better_py import AsyncMaybe
async_maybe = AsyncMaybe.some(42)Use this when you have a value to wrap in an AsyncMaybe context.
Creating Empty Values
The AsyncMaybe.nothing method creates an empty AsyncMaybe.
from better_py import AsyncMaybe
empty = AsyncMaybe.nothing()Use this when you need to represent the absence of a value.
Creating from Optional Values
The AsyncMaybe.from_value method creates an AsyncMaybe from an optional value.
from better_py import AsyncMaybe
AsyncMaybe.from_value(42) # Some(42)
AsyncMaybe.from_value(None) # NothingUse this to convert existing optional values into AsyncMaybe.
Checking Variants
Checking for Values
The is_some_async method checks if the AsyncMaybe contains a value.
from better_py import AsyncMaybe
await AsyncMaybe.some(42).is_some_async() # True
await AsyncMaybe.nothing().is_some_async() # FalseUse this to check for the presence of a value.
Checking for Empty
The is_nothing_async method checks if the AsyncMaybe is empty.
from better_py import AsyncMaybe
await AsyncMaybe.some(42).is_nothing_async() # False
await AsyncMaybe.nothing().is_nothing_async() # TrueUse this to check for the absence of a value.
Extracting Values
Getting Values or Errors
The unwrap method returns the value or raises an error if empty.
from better_py import AsyncMaybe
await AsyncMaybe.some(42).unwrap() # 42
await AsyncMaybe.nothing().unwrap() # Raises ValueErrorUse this only when you're certain the AsyncMaybe contains a value.
Getting Values or Defaults
The unwrap_or_else method returns the value or computes a default.
from better_py import AsyncMaybe
# With value default
await AsyncMaybe.some(42).unwrap_or_else(0) # 42
await AsyncMaybe.nothing().unwrap_or_else(0) # 0
# With function default
await AsyncMaybe.nothing().unwrap_or_else(lambda: expensive()) # expensive()Use this when you need safe access to the value with a fallback.
Transforming Values
Mapping Values
The map method applies a function to the contained value.
from better_py import AsyncMaybe
AsyncMaybe.some(5).map(lambda x: x * 2) # Some(10)
AsyncMaybe.nothing().map(lambda x: x * 2) # NothingWhen map is called on Nothing, it returns Nothing unchanged.
Mapping Async Functions
The map_async method applies an async function to the contained value.
from better_py import AsyncMaybe
async def fetch(x: int) -> str:
return await api.get(f"/items/{x}")
await AsyncMaybe.some(5).map_async(fetch) # Some(result)
await AsyncMaybe.nothing().map_async(fetch) # NothingUse this when the transformation function is async.
Chaining Operations
Binding Async Operations
The bind method chains AsyncMaybe-returning operations.
from better_py import AsyncMaybe
async def get_user(user_id: int) -> AsyncMaybe[dict]:
user = await db.fetch(user_id)
return AsyncMaybe.from_value(user)
async def get_orders(user: dict) -> AsyncMaybe[list]:
orders = await db.fetch_orders(user["id"])
return AsyncMaybe.from_value(orders)
# Chain operations
orders = await get_user(1).bind(get_orders) # Some([...]) or NothingUse bind for sequential operations where each step depends on the previous one.
Converting to Maybe
Sync Conversion
The to_maybe method converts AsyncMaybe to a regular Maybe.
from better_py import AsyncMaybe, Some, Nothing
AsyncMaybe.some(42).to_maybe() # Some(42)
AsyncMaybe.nothing().to_maybe() # NothingUse this when you need to integrate with non-async code that uses Maybe.
Real-World Pattern: Async Database Queries
from better_py import AsyncMaybe
async def find_user(user_id: int) -> AsyncMaybe[dict]:
user = await database.query(f"SELECT * FROM users WHERE id = {user_id}")
return AsyncMaybe.from_value(user)
async def get_user_email(user_id: int) -> AsyncMaybe[str]:
return (await find_user(user_id)
.bind(async lambda user: AsyncMaybe.from_value(user.get("email"))))
async def get_user_orders(user_id: int) -> AsyncMaybe[list]:
return (await find_user(user_id)
.bind(async lambda user: fetch_orders(user["id"])))
# Use the chained operations
email = await get_user_email(1) # Some("alice@example.com") or Nothing
orders = await get_user_orders(1) # Some([...]) or NothingThis pattern shows AsyncMaybe's power: async database operations that might not find data are handled gracefully, and operations can be chained without nested if statements.
When to Use
Use AsyncMaybe when:
- Working with async code that returns optional values
- You need awaitable operations
- You're using asyncio
- You want to chain async operations that might not return values
- You need safe handling of missing async values
Don't use AsyncMaybe when:
- You're not using async (use
Maybeinstead) - You need error messages (use
AsyncResultinstead) - Values should always be present (use plain types)
- You're doing simple async operations (plain async/await is fine)
See Also
- Maybe - Non-async optional values
- AsyncResult - Async error handling
- Try - Exception handling