Use server calls safely inside effects

Most bindings and effects finish immediately. An effect can also call a server function or wait for browser work. While it waits, the rest of your app can keep responding to the user.

Anvil calls this pause in client Python a suspension. You do not need to resume the function yourself, but you do need to know which state changes will run the effect again and what happens if two runs overlap.

Read the state you need before waiting

Reactive watches values that an effect reads before it first has to wait:

@effect
def load_customer(self):
    customer_id = self.customer_id
    show_email = self.show_email

    customer = anvil.server.call("get_customer", customer_id)

    self.name_label.text = customer["name"]
    self.email_label.visible = show_email

After this run has finished, changing customer_id or show_email can run the effect again. If the effect first read show_email after the server call returned, changing it would not run this effect. Do not rely on a change made while the server call is still waiting to start a new run; the next section shows a safer pattern for rapidly changing input.

Put every reactive value that should trigger the effect into a local variable before the first operation that might wait. This rule applies to any operation that pauses client Python, not only anvil.server.call().

The wait may be hidden inside a helper. This order is fragile if initialise() sometimes calls the server:

def search(self):
    self.initialise()
    version = self.version  # Too late if initialise() had to wait.
    return self.view.search()

Read the trigger first:

def search(self):
    version = self.version
    self.initialise()
    return self.view.search()

Where possible, load data explicitly in an event handler or page-lifecycle method and keep computed values synchronous. A property that looks like a read but sometimes loads from the server is difficult to reason about.

The function can wait more than once and will continue in order. Errors raised after it resumes still appear through Anvil's normal error handling.

Do not expect an older request to be cancelled

Suppose an effect searches the server whenever a user changes some text. If the user types again while the first search is waiting, both searches may complete. The older result is not automatically cancelled and may arrive last.

For user actions that can happen quickly, an event handler with a request number makes the intended behavior explicit:

def __init__(self, **properties):
    super().__init__(**properties)
    self._search_request = 0


@handle("search_box", "change")
def search_box_change(self, **event_args):
    self._search_request += 1
    request = self._search_request
    query = self.search_box.text

    results = anvil.server.call("search", query)

    if request != self._search_request:
        return

    self.results_grid.items = results

This ignores an older result if the user has started another search in the meantime.

Warning

Removing a Form does not cancel work that is already waiting. Do not rely on a component's removal to prevent the rest of an effect or event handler from running.

For state that changes synchronously, bindings, computed values, and effects can manage updates directly. For a user-driven workflow that waits for the server, an event handler is often easier to reason about.