This page covers deferred calls, fan-out, and nested calls to a deployed Chalk Function, and how its errors reach the caller.


​
Deferred calls with defer() and get()

.remote(...) blocks for the entire round trip. Use .defer(...) instead when you want to submit a call and collect its result later; it enqueues the call and returns a FunctionCallHandle immediately, without waiting for the function to run. This example calls the add function from Defining a function:

import chalkcompute

add = chalkcompute.RemoteFunction.from_name("add")

handle = add.defer(3, 5)
# ...do other work while the call runs...
result = handle.get()
print(result)  # 8

handle.get(timeout=None) blocks until the result is ready and returns the same value .remote(...) would have returned.

Each handle exposes the server-assigned call_id it was enqueued under:

handle = add.defer(3, 5)
print(handle.call_id)

​
Fan-out and gather()

Because .defer(...) returns immediately, you can enqueue many calls up front and collect their results afterward. Use chalkcompute.gather(...) to wait for a batch of handles concurrently and get their results back in order:

import chalkcompute

handles = [classify.defer(text) for text in texts]
results = chalkcompute.gather(handles)

gather(...) waits for the handles concurrently rather than one at a time, so it returns as soon as the slowest call finishes. Pass timeout to bound the whole wait:

results = chalkcompute.gather(handles, timeout=30.0)

Inside a running event loop, gather(...) returns an awaitable instead of blocking: await chalkcompute.gather(handles).

Each deferred call is still subject to the function’s own concurrency and rate limit policies, so fanning out this way doesn’t bypass those caps.

​
Timeouts

Pass timeout (seconds) to bound how long get() waits:

try:
    result = handle.get(timeout=5.0)
except TimeoutError:
    print("still running")

A timeout only stops waiting locally. It does not cancel the underlying call, which keeps running and can still be collected with another get().

​
Errors from deferred calls

If the function raised an exception, .get() raises chalkcompute.FunctionError describing the failure. Transient infrastructure failures, such as a dropped connection between the queue and the worker, are retried automatically before get() ever sees them; only failures from the function body itself, or infra failures that exhaust retries, are raised to the caller.


​
Nested function calls

A function can call other functions with .remote(...). Each inner call is dispatched independently, so you can compose pipelines where each step runs on its own infrastructure. This example calls the transcribe async generator from Streaming results:

import chalkcompute

@chalkcompute.function
def translate(text: str, target_lang: str) -> str:
    from transformers import pipeline
    translator = pipeline("translation", model=f"Helsinki-NLP/opus-mt-en-{target_lang}")
    return translator(text)[0]["translation_text"]


@chalkcompute.function
async def transcribe_and_translate(audio_uri: str, target_lang: str):
    """Transcribe audio, then translate each segment."""
    translate.wait_ready()

    async for segment in transcribe.remote(audio_uri):
        translated = await translate.remote(segment["text"], target_lang)
        yield {
            **segment,
            "translated": translated,
        }

The caller sees a single stream of translated segments:

import asyncio

transcribe_and_translate.wait_ready()

async def main():
    async for seg in transcribe_and_translate.remote(
        "https://example.com/meeting.wav",
        target_lang="es",
    ):
        print(f"[{seg['start']:.1f}s] {seg['translated']}")

asyncio.run(main())
[0.0s]  Bienvenidos a la revisión trimestral.
[3.2s]  Comencemos con las cifras de ingresos.
[6.1s]  El tercer trimestre llegó a 4,2 millones, un 18 por ciento más.

The outer function composes two inner calls: transcribe (async generator) and translate (single-value). Each runs on its own compute configuration.


​
Error handling

If a function raises an exception, the caller receives it as a RuntimeError with the original exception type and message:

import chalkcompute

@chalkcompute.function
def divide(a: float, b: float) -> float:
    return a / b

divide.wait_ready()

try:
    divide.remote(1.0, 0.0)
except RuntimeError as e:
    print(e)  # ZeroDivisionError: division by zero

For async generators, an exception mid-stream terminates the iterator. Any segments already yielded are still available to the caller; only subsequent iteration raises:

import chalkcompute

@chalkcompute.function
async def fragile_gen():
    yield "ok-1"
    yield "ok-2"
    raise ValueError("something went wrong")

import asyncio

async def main():
    async for item in fragile_gen.remote():
        print(item)

asyncio.run(main())
# ok-1
# ok-2
# RuntimeError: ValueError: something went wrong