Asyncs and promises
Asynchronous tasks are the only way to make network calls in Brossa. This ensures that external services can never freeze or delay the entire policy evaluation process. Whether you’re fetching currency exchange rates, calling a risk scoring API, or integrating with any other external service, these operations must be handled asynchronously, even when the response might be fast.
Beyond network calls, async tasks are also ideal for any inherently long-running computations, such as complex risk analysis that might take hours to complete.
Asyncs and promises provide the mechanism for handling these operations by separating task definition from execution, giving you explicit control over when these operations run, while maintaining full visibility into their state and results.
Core components
The system uses a three-part approach:
-
Asyncs (
Async<a>) define what task should be performed, but don’t execute it. -
Promises (
Promise<a>) represent a running instance of that task. -
wait() retrieves the final result when the task completes.
This separation means you can define a task once, then create multiple running instances of it as needed. Each promise gives you a handle to track a specific job’s progress and retrieve its result.
Key benefits:
-
Tasks only run when explicitly triggered.
-
Full visibility into task state and dependencies.
-
Safe handling of long-running operations.
-
Results are preserved and can be accessed multiple times.
Asyncs
An Async<a> represents a deferred, long-running computation that will eventually return a value of type a.
It does not perform any action when evaluated – it only describes the action.
Asyncs are useful for actions that should not run during normal policy evaluation, and instead require explicit triggering.
Syntax
Declare an async using the foreign async syntax.
The foreign keyword indicates that the actual implementation is provided externally, and is not written in Brossa code.
The configuration keys like service, operation, and version are defined by the specific module being used:
foreign async <module>
{service: "<SERVICE>", operation: "<OPERATION>", version: <VERSION>}
functionName(Input): Output;
Example using the http module (our most commonly used foreign async module):
foreign async http
{service: "HTTP_CLIENT", operation: "GET", version: 1}
fetchData(Input): Output;
This defines a global function fetchData which returns a value of type Async<Output> when given an Input.
You cannot execute asyncs directly within Brossa.
To perform the action, you must use a Promise.
Note that each module dependency, such as http or sequelhub, defines its own available configuration keys and values.
|
Promises
A Promise<a> represents a specific instance of an async task that has been started, and which will eventually produce a result of type a.
You can think of it as a ticket stub for a running background job: it proves the job is running or completed, but it doesn’t contain the result itself.
When you’re ready to get the actual result, wait() is like scanning that ticket at a booth to pick up the final output.
You don’t get the result immediately.
Instead, you define a Promise, trigger the async, and later retrieve the result using wait().
This model makes it possible to:
-
Separate definition from execution.
-
Control when tasks run.
-
Track and retrieve results safely.
| When using promises in a spec, you don’t need to worry about the handling of errors or retries. For example, if a remote service like currency conversion fails with a 500 error, Brossa handles this gracefully behind the scenes. You only need to define what async action you want, and how to consume its result. |
Syntax
<datapoint>: Promise<Output>;
<datapoint> has async <asyncFunction>(<input>);
Example:
my_risk_score: Promise<SequelAI.Output>;
my_risk_score has async SequelAI.riskScore({ ... });
This starts the async job and assigns the resulting promise to the data point.
Execution behaviour
-
By default, the system starts the task automatically once all required inputs are available.
-
If you annotate the data point with
@no-auto-start, the task will not run until manually triggered via an API call.
@no-auto-start
my_risk_score: Promise<SequelAI.Output>;
Until the promise resolves, any logic depending on its result will remain blocked.
Using wait()
To retrieve the actual result of a completed promise, use the wait() function:
wait(my_risk_score)
This extracts the result from the Promise.
While the underlying representation is a numeric ID, the language treats the result as a properly typed value.
You must use wait() because Brossa data points cannot store structured or nested output directly.
Since async results often contain complex data structures (records, lists, etc.), they cannot be stored in regular data points.
Instead, promises store a numeric ID that references the actual result in a separate location.
The wait() function uses this ID as a "key" to look up and return the full structured data when you need it.
Readiness
Brossa tracks the readiness state for each Promise data point.
This indicates whether all required inputs and dependencies are present for the async to run.
A promise is "ready" when:
-
All input values needed by the async function are available.
-
Any data points that the async depends on have been evaluated.
-
The promise hasn’t been started yet (or needs to be restarted).
Based on this readiness state:
-
If not ready: You’ll be prompted to complete missing data or resolve dependencies before the task can run.
-
If ready: You can manually trigger the action (for tasks marked with
@no-auto-start) or the task will start automatically.