Library

std/async

Run async functions, wait on time, and combine awaitables.

1 file lib/std/async/async.vader 10 exports
import "std/async"
import "std/io"

fetch :: fn(n: i64) -> Async<i64> {
    await sleep(n)
    return n * 10
}

main :: fn() -> i32 {
    results :: await join_all([fetch(3), fetch(1), fetch(2)])
    println("${results}")   // [30, 10, 20]
    return 0
}

A function that uses await is async: it declares -> Async<T> and returns a T. Calling it gives an Async<T>, which await waits on from another async function and block_on runs to completion from one that is not; main may await and keep its i32. The combinators are join_all, try_join_all, race, join_any, timeout and share.

Single-threaded, timer-driven: the scheduler state (a virtual clock + a min-heap of wake deadlines) lives in the runtime (native C / VM host), reached through the std/runtime sched_* intrinsics.

Types

TimeoutError :: struct

Error completing a timeout when its deadline elapses before the awaited value is ready.

Fields

ms: i64

The deadline that elapsed, in milliseconds.

Implements Error message :: fn(self) -> string

Functions

sleep :: fn(ms: i64) -> Async<null>

Suspend the current coroutine for at least ms virtual milliseconds. A factory (it constructs the awaitable, it does not itself await), so it is an ordinary function returning an Async, not a coroutine.

Parameters

msi64 the minimum delay, in milliseconds.

Returns

Async<null> an awaitable that completes once the delay elapses.

Example

await sleep(10)   // null, 10 ms later
run_async_main :: fn(root: Async<i32>) -> i32

Drive an async main's root frame to completion and return its exit code. Compiler-invoked : when main awaits, the program entry calls this with main()'s Async<i32> frame. Resumes the root ; whenever it parks, blocks on the nearest armed timer (sched_park) and re-drives, never spins.

Parameters

rootAsync<i32> the Async<i32> frame produced by calling the async main.

Returns

i32 main's completed i32 exit code.

Errors

  • panics on a park with no timer armed: every task is suspended with nothing left to wake it (a deadlock).
block_on :: fn<T>(root: Async<T>) -> T

Drive any awaitable to completion from synchronous code, returning its result. Resumes root and, whenever it parks, blocks on the nearest armed timer (sched_park) then re-drives, never spins. Needs no async main: it works over the global scheduler state, so a plain sync caller (e.g. the CLI spawning subprocesses) can fan out awaitables and join them here.

Parameters

rootAsync<T> the awaitable to run to completion.

Returns

T its completed value.

Errors

  • panics on a park with nothing armed to wake it (a deadlock).

Example

block_on(sleep(10))   // null: the caller need not be async
share :: fn<T>(task: Async<T>) -> Async<T>

Share task between any number of awaiters : the first resume drives it, and once it completes every later one hands back its result, where a finished coroutine would answer Suspended. Cancelling the shared awaitable leaves the task to the others. The task an async @singleton hands every call.

Parameters

taskAsync<T> the awaitable to share.

Returns

Async<T> an awaitable every awaiter may await.

Errors

  • panics when the task awaits its own shared awaitable.

Example

once :: share(sleep(10))
await join_all([once, once])   // [null, null], one timer for both
join_all :: fn<T>(xs: Async<T>[]) -> Async<T[]>

Await a group of awaitables concurrently, yielding their results in input order. await join_all(xs) completes when every element of xs has completed.

Parameters

xsAsync<T>[] the awaitables to join.

Returns

Async<T[]> an awaitable of the results, index-aligned with xs.

Example

await join_all([sleep(20), sleep(10)])   // [null, null], after 20 ms
try_join_all :: fn<U, E>(xs: Async<U | E>[]) -> Async<(U | E)[] | Error>

Concurrent join of FALLIBLE awaitables (children of type U | E). Runs every child (like join_all: no cancellation in v1, so failure latency is the slowest child), then (scanning in input order) completes with the FIRST error, or the full results if none failed.

The successes come back as (U | E)[], each element typed as the child union: narrow per element at the use site if you need the raw U.

Parameters

xsAsync<U | E>[] the fallible awaitables to join.

Returns

Async<(U | E)[] | Error> the results in input order, or the first Error.
race :: fn<T>(xs: Async<T>[]) -> Async<T>

Race a group of awaitables : complete with the FIRST to settle (value or error), cancelling the losers so they leave no orphaned work. Unlike join_all, the return type is a single T: the winner's result.

Parameters

xsAsync<T>[] the awaitables to race.

Returns

Async<T> an awaitable of the first to settle.

Example

await race([sleep(20), sleep(10)])   // null, after 10 ms: the 20 ms sleep is cancelled
join_any :: fn<U, E>(xs: Async<U | E>[]) -> Async<U | E>

Await a group of FALLIBLE awaitables (children of type U | E), completing with the FIRST success and cancelling the rest. If every child fails, completes with the last error. The dual of try_join_all (first success vs first error).

Parameters

xsAsync<U | E>[] the fallible awaitables to race for a success.

Returns

Async<U | E> the first successful result, or the last error if all fail.
timeout :: fn<T>(ms: i64, x: Async<T>) -> Async<T | TimeoutError>

Await x, but fail with a TimeoutError if it is not ready within ms virtual milliseconds. The losing side (the child on timeout, the timer on success) is cancelled.

Parameters

msi64 the deadline, in milliseconds.
xAsync<T> the awaitable to bound.

Returns

Async<T | TimeoutError> x's result, or a TimeoutError if the deadline elapses first.

Example

await timeout(10, sleep(50))   // a TimeoutError, after 10 ms
await timeout(50, sleep(10))   // null, after 10 ms
Written by vader doc. Icons: Codicons, CC BY 4.0. Fonts: Atkinson Hyperlegible, JetBrains Mono, OFL.