Asynchronous code
Use asynchronous functions when work may suspend while waiting for a platform operation or another task. Noodle keeps success, business failure, and cancellation as distinct typed outcomes.
Async function types
Section titled “Async function types”errortype LoadError LoadFailed(String)end
async func load_count() throws LoadError -> Int do 42endThe declared body result is Int. Callers receive
Task[Int, LoadError]. An async function without throws uses the empty
standard-library error set NoError.
Calling an async function starts its task; it does not implicitly wait for the result.
Await a task
Section titled “Await a task”.await() is available only inside an async context:
errortype LoadError LoadFailed(String)end
async func load_count() throws LoadError -> Int do 42end
async func doubled_count() throws LoadError -> Int do outcome = load_count().await(); switch outcome case AsyncResult::Ok(value) then value * 2 case AsyncResult::Err(error) then throw error case AsyncResult::Cancelled(_) then 0 endendWaiting produces AsyncResult[T, E] with three constructors:
Ok(T)for success;Err(E)for the task’s declared business error;Cancelled(CancelReason)when the task is abandoned.
Cancellation is not merged into the error set.
Propagate an async outcome
Section titled “Propagate an async outcome”Postfix ! on AsyncResult extracts Ok and propagates both Err and
Cancelled through the enclosing async function:
errortype LoadError LoadFailed(String)end
async func load_count() throws LoadError -> Int do 42end
async func doubled_count() throws LoadError -> Int do value = load_count().await()!; value * 2endUse this form when the current function does not own recovery. Use an explicit
switch when each outcome needs local behavior.
Async program entry points
Section titled “Async program entry points”An entry file may declare async func main() -> Unit. The program runner waits
for it:
errortype LoadError LoadFailed(String)end
async func load_count() throws LoadError -> Int do 42end
async func doubled_count() throws LoadError -> Int do value = load_count().await()!; value * 2end
async func main() -> Unit do result = doubled_count().await(); switch result case AsyncResult::Ok(value) then Debug.trace(value) case AsyncResult::Err(LoadError::LoadFailed(message)) then Console.println(message) case AsyncResult::Cancelled(_) then Console.println("cancelled") end;endAt the outer entry boundary, success exits normally, an unhandled business error exits unsuccessfully, and cancellation uses the cancellation exit path.
Async bindings
Section titled “Async bindings”An async binding starts its initializer immediately in a background task. Its
type is AsyncResult[T, E], and referencing the binding waits for that task;
the reference is legal only inside an async context:
async func load_name() -> String do "Ada"end
async func greeting() -> String do async name = load_name().await()!; "Hello, " ++ name!endThe initializer is checked as an anonymous async body, so it may use .await()
and postfix !. A plain value is lifted to AsyncResult::Ok; errors propagated
inside the initializer become that task’s error outcome. The compiler reports
an async binding that is never referenced.
Multiple bindings start before the rest of the block runs, so they can overlap:
async func load_user() -> String do "Ada" endasync func load_plan() -> String do "Pro" end
async func describe() -> String do async user = load_user().await()!; async plan = load_plan().await()!; user! ++ " / " ++ plan!endUse an async binding when the lexical scope clearly owns the concurrent work. Call an async function directly when explicit task ownership and waiting make the control flow easier to see.
Cancellation behavior (Experimental)
Section titled “Cancellation behavior (Experimental)”Cancellation is cooperative. Waiting on a child task registers cancellation through the wait chain, but a long synchronous computation is not interrupted in the middle of a segment. Cancelling a settled task has no effect, and repeated cancellation is idempotent.
Cancellation behavior is experimental and may change as the runtime model stabilizes. In particular, an unconsumed async binding is cancelled when its lexical block exits; consumed tasks are unaffected, and cancellation is idempotent.
Task-producing extern func declarations form the platform boundary. Their
host companion must bridge the platform promise into the standard Task model
so cancellation metadata and AsyncResult semantics remain intact.
Next: Tests, including asynchronous tests.