reflex-dev / reflex

🕸️ Web apps in pure Python 🐍
https://reflex.dev
Apache License 2.0
19.88k stars 1.15k forks source link

[Enhancement] Async `cached_var` in the state #4022

Open clemlesne opened 1 month ago

clemlesne commented 1 month ago

Motivations

  1. Cached vars are to ease the load on the system and reduce network calls
  2. Async calls are common in Python to load data from dependencies, like databases, caches, and config stores
  3. As of today, to load a data asynchronously and caching it in the state, it is required to develop a lot
    • A synchronous getter
    • A loaded state as bool to be alerted of the loading state
    • A background task to load the data
    • A if/else in the UI to wait for the data to be loaded

Example of expected usage

class MyState(rx.State):
    user_id: str

    @rx.cached_var
    async def user(self) -> User | None:
        return await db.load_user_by_id(self.user_id)

Implementation

Under the hood, this could be achieved with a background task loading the data, the cached var returning None until it is loaded.

Or, the async task can be wrapped by asyncio to be called synchronously from the app: asyncio.get_event_loop().run_until_complete(func(...)).

masenf commented 3 weeks ago

i think this is a really good idea. in 0.6.0 we replaced the property-based cached var with our own custom descriptor, so it should theoretically be possible to implement this now, but some parts may not work properly; for example if i access an async property via getattr from a non-async function, what should happen?

clemlesne commented 3 weeks ago

Well good question.

From a developer point of view, it could return the expected value or None (not loaded).

But how to know if the cached var is None or if it is not loaded? We could implement an object with a loaded state and a return value (default to None); maybe overkill.