> ## Documentation Index
> Fetch the complete documentation index at: https://docs.samplehc.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Async Results

> Using suspend and resume to wait for async results

## Overview

Some APIs return an `async_result_id`. While this is useful if you want to continue the workflow while it is running, sometimes you want to wait for the operation to complete. This is where the `suspend_fn` method comes in.

## Suspending a Workflow

To suspend a workflow, you can use the `ctx.suspend_fn` method within a `Step` function. This will suspend the workflow until a resume event is received.

The standard way to trigger a resume event when a `async_result_id` is ready is to use the [`resume_when_complete`](/api-reference/workflow-run/resume-workflow-after-async-task) method.

```python theme={null}
from workflows_py.workflow import ScreenStep, Step, Workflow
from client_manager import SampleHealthcareClient

def reasoning_sync(ctx):
    if ctx.resume_data:
        return ctx.resume_data

    documents = ctx.get_start_data()["documents"]
    client = SampleHealthcareClient()
    async_result_id = client.v2.documents.legacy.reason(
        documents=documents,
        task={
            "id": "reasoning-task-1",
            "description": "Extract key information from the document",
            "label": "Information Extraction",
            "type": "reasoning",
        },
    ).async_result_id

    client.v2.workflow_runs.resume_when_complete(
        async_result_id=async_result_id,
    )

    ctx.suspend_fn({"type": "reasoning", "asyncResultId": async_result_id})

def generate_document(ctx):
    ...

workflow = Workflow()
workflow.then(
    Step("reasoning", reasoning_sync)
).then(
    Step("generate-document", generate_document)
)
```

Note that the same function (`reasoning_sync`) will be called when the workflow is resumed, and the resume data will be passed via the `ctx.resume_data` property (in this case, the result from the `async_result_id`).

<Warning>
  Because the step function runs again from the top on resume, guard it with
  `if ctx.resume_data: return ctx.resume_data` as the first line. Without this
  guard, any side effects in the step (API calls, SQL writes, emails) will
  execute a second time when the workflow resumes.
</Warning>

### Suspend Payloads

The payload passed to `ctx.suspend_fn` is stored with the suspended task and is
useful for observability (for example, recording which `async_result_id` the
step is waiting on). Except for payloads with `"type": "screen"` (see below),
the platform does not interpret the payload — you can use any JSON-serializable
shape that helps you debug suspended runs.

### Resume Priority

Resumes are processed from a queue. By default they run at background priority,
which is appropriate for scheduled and bulk workflows. When a user is actively
waiting on the result — for example, watching a loading screen — pass
`priority: "interactive"` so the resume is processed sooner:

```python theme={null}
client.v2.workflow_runs.resume_when_complete(
    async_result_id=async_result_id,
    extra_body={"priority": "interactive"},
)
```

<Note>
  If your SDK version does not accept `priority` as a keyword argument, pass it
  via `extra_body` as shown above.
</Note>

## Showing a Loading Screen

While waiting for async results to complete, you can show a loading screen to provide visual feedback to users. To display a loading screen, call `ctx.suspend_fn` with a screen payload **before the function returns**:

```python theme={null}
ctx.suspend_fn({
    "type": "screen",
    "props": {"asyncResultId": async_result_id, "taskDescription": "Processing documents"},
    "screenPath": "./screens/loading-screen.tsx"
})
```

**Important Notes:**

* You must call `suspend_fn` before the function returns to ensure the loading screen is displayed
* Any props passed in the `props` parameter will be passed to the React component at `screenPath` as props
* This allows you to pass necessary metadata (such as the `async_result_id` or task descriptions) that the loading screen can use to provide more detailed feedback

This will display the loading screen specified in the `screenPath` while the workflow is suspended, providing a better user experience during long-running operations.

## Delays and Scheduled Resumes

Sometimes you want a workflow to wait for a period of time rather than for an
external operation — for example, to retry a check the next day or to follow up
on a request after a waiting period. The [`sleep`](/api-reference) endpoint
returns an `async_result_id` that completes after the given delay, so you can
use the same suspend and resume pattern as any other async result.

```python theme={null}
from workflows_py.workflow import Step, Workflow
from client_manager import SampleHealthcareClient

def wait_one_day(ctx):
    if ctx.resume_data:
        return ctx.resume_data

    client = SampleHealthcareClient()
    sleep_result = client.v2.async_results.sleep(
        delay=24 * 60 * 60 * 1000,  # delay in milliseconds
    )

    client.v2.workflow_runs.resume_when_complete(
        async_result_id=sleep_result.async_result_id,
    )

    ctx.suspend_fn({"type": "sleep", "asyncResultId": sleep_result.async_result_id})

workflow = Workflow()
workflow.then(
    Step("wait-one-day", wait_one_day)
)
```

`sleep` accepts either of the following parameters:

* **delay**: Time to wait in **milliseconds**.
* **resume\_at**: An ISO 8601 timestamp to resume at, e.g. `(datetime.now(timezone.utc) + timedelta(days=7)).isoformat()`.

<Tip>
  Suspending is durable — the step is not running while it waits, so it is safe
  to sleep for hours or days. Prefer this over `time.sleep()`, which blocks the
  step for the entire delay and is subject to the step's execution time limit.
</Tip>
