NonBlocking

Call functions in a non-blocking way.

In a blocking execution, the next line of code will not be executed until the current line has completed.

In contrast, non-blocking execution allows the next line to be executed without waiting for the current line to complete.

Note

This module cannot be used to call server functions simultaneously, as Anvil server calls are queued.

A suitable use case for this library is when you want to perform an action without waiting for a response, such as updating a database after making changes on the client side.

Examples

Call a server function

After updating the client, call a server function to update the database. In this example, we don’t care about the return value.

from anvil_extras.non_blocking import call_async

def button_click(self, **event_args):
    self.update_database()
    self.open_form("Form1")

def update_database(self):
    # Unlike anvil.server.call, we do not wait for the call to return
    call_async("update", self.item)

Handle return values and errors

If you want to handle the return value or any errors, you can provide result and error handlers.

from anvil_extras.non_blocking import call_async

def handle_result(self, res):
    print(res)
    Notification("successfully saved").show()

def handle_error(self, err):
    print(err)
    Notification("there was a problem", style="danger").show()

def update_database(self, **event_args):
    call_async("update", self.item).on_result(self.handle_result, self.handle_error)
    # Equivalent to
    async_call = call_async("update", self.item)
    async_call.on_result(self.handle_result, self.handle_error)
    # Equivalent to
    async_call = call_async("update", self.item)
    async_call.on_result(self.handle_result)
    async_call.on_error(self.handle_error)

repeat

Call a function repeatedly using the repeat() function. The function will be called after each specified interval in seconds. To end or cancel the repeated call, use the cancel method.

from anvil_extras import non_blocking

i = 0
def do_heartbeat():
    global heartbeat, i
    if i >= 42:
        heartbeat.cancel()
        # equivalent to non_blocking.cancel(heartbeat)
    print("da dum")
    i += 1

heartbeat = non_blocking.repeat(do_heartbeat, 1)

defer

Call a function after a set period of time using the defer() function. To cancel the deferred call, use the cancel() method.

from anvil_extras import non_blocking

class Form1(Form1Template):
    def __init__(self, **properties):
        ...
        self.deferred_search = None

    def update_search_results(self):
        search_results = anvil.server.call_s("search_results", self.search_box.text)
        # do something with search_results

    def search_box_change(self, **event_args):
        # cancel the existing deferred_search
        non_blocking.cancel(self.deferred_search)
        self.deferred_search = non_blocking.defer(self.update_search_results, 0.3)

In this example we call self.update_search_results() only when the user has stopped typing for 0.3 seconds. If the user starts typing again before 0.3 seconds is up, the deferred call is cancelled. This prevents us calling the server too often.

API

call_async(fn, *args, **kws)
call_async(fn_name, *args, **kws)

Returns an AsyncCall object. The fn will be called in a non-blocking way.

If the first argument is a string, then the server function with the name fn_name will be called in a non-blocking way.

wait_for(async_call_object)

Blocks until the AsyncCall object has finished executing.

class AsyncCall

Don’t instantiate this class directly; instead, use the functions above.

on_result(self, result_handler, error_handler=None)

Provide a result handler to handle the return value of the non-blocking call. Provide an optional error handler to handle the error if the non-blocking call raises an exception. Both handlers should take a single argument.

Returns self.

on_error(self, error_handler)

Provide an error handler that will be called if the non-blocking call raises an exception. The handler should take a single argument, the exception to handle.

Returns self.

await_result(self)

Waits for the non-blocking call to finish executing and returns the result. Raises an exception if the non-blocking call raised an exception.

property result

If the non-blocking call has not yet completed, raises a RuntimeError.

If the non-blocking call has completed, returns the result. Raises an exception if the non-blocking call raised an exception.

property error

If the non-blocking call has not yet completed, raises a RuntimeError.

If the non-blocking call raised an exception, the exception raised can be accessed using the error property. The error will be None if the non-blocking call returned a result.

property status

One of "PENDING", "FULFILLED", "REJECTED".

cancel(ref)

Cancel an active call to delay or defer. The first argument should be None or the return value from a call to delay or defer.

Calling cancel(ref) is equivalent to ref.cancel(). You may wish to use cancel(ref) if you start with a placeholder ref equal to None. See the defer example above.

repeat(fn, interval)

Repeatedly call a function with a set interval (in seconds).

  • fn should be a callable that takes no arguments.

  • interval should be an int or float representing the time in seconds between function calls.

The function is called in a non-blocking way.

A call to repeat returns a RepeatRef object that has a .cancel() method.

Calling the .cancel() method will stop the next repeated call from executing.

defer(fn, delay)

Defer a function call after a set period of time has elapsed (in seconds).

  • fn should be a callable that takes no arguments.

  • delay should be an int or float representing the time in seconds.

The function is called in a non-blocking way. A call to defer returns a DeferRef object that has a .cancel() method.

Calling the .cancel() method will stop the deferred function from executing.