25. Async and Await
An async fn is a function whose body may suspend at an
await and resume later, delivering its single result through a
Future<T>. Async functions are built directly on
coroutines: an async fn compiles to a
coroutine whose suspension points are awaits. Calling one runs its
body eagerly up to the first await (or to completion) and
hands back a Future.
25.1 Async Functions and await
Declare an async function with async fn. Inside it,
await unwraps a Future<T> to its value
T. The function's external return type is
Future<T> even though its body produces a plain T.
async fn compute(x Int) Int {
let a = await ready(x + 1);
let b = await ready(a * 10);
b + 5
}
-- compute returns Future<Int>; await it to get the Int
let r = await compute(4);
r println; -- 55
At the top level (or any non-async context) await blocks until the
future resolves; inside an async fn it suspends cooperatively and
resumes when the awaited future is ready. It is the same keyword either way.
25.2 The Future Type and ready
ready(x) wraps a value in an already-resolved
Future<T> — handy for returning a constant from an async
context, or in tests.
let f = ready(42); -- f : Future<Int>
let v = await f; -- 42
-- An async fn that takes and returns values:
async fn greet(name String) String { "hi " $ name }
let g = greet("Ada"); -- runs eagerly; g : Future<String>
(await g) println; -- hi Ada
25.3 Awaiting Time with delay
delay(beats) returns a Future<Void> that resolves
after the given number of beats. Awaiting it suspends the async function until
that time arrives, so a beat-based sequence reads top to bottom.
The beats are logical time. On a silo VM the audio clock drives them, so
await delay there waits in real, sample-accurate time. On the main (NRT)
VM a top-level await advances the timeline straight to the next timer, so
the sequence above prints immediately in the right order without taking three
beats of wall-clock time — which is also what lets offline renders run faster
than real time. When code really must wait in wall-clock time (pacing a script
against the outside world), use await delayReal(seconds) from the
clock module; to await beats on the live tempo clock (tracking tempo
changes and ramps like a sched callback), use
await delayBeats(beats) (see the FFI Guide, §11.7).
async fn afterDelay(tag String, beats Float) Void {
await delay(beats);
tag println;
}
-- Start three out of order; they resolve in beat order.
let a = afterDelay("A(3)", 3.0);
let b = afterDelay("B(1)", 1.0);
let c = afterDelay("C(2)", 2.0);
await a; await b; await c;
-- B(1) C(2) A(3)
25.4 Combinators: awaitAll and gather
The futures module provides combinators over a list of futures.
awaitAll is a barrier — it returns once every future has
resolved. gather also collects their values, in argument order
(independent of the order they resolved in).
import std.futures.*;
-- awaitAll: wait for all of them (a barrier)
await awaitAll([afterDelay("x", 1.0), afterDelay("y", 2.0)]);
async fn load(id Int, beats Float) Int {
await delay(beats);
id * 10
}
-- gather: results come back in argument order, not resolve order
let xs = await gather([load(1, 2.0), load(2, 1.0), load(3, 3.0)]);
xs println; -- [10, 20, 30]
Because each load(...) runs eagerly to its first await
before gather is called, the three loads are all in flight at once;
gather just waits for them and assembles the results in order.
25.5 How It Works
- An async fn is a coroutine.
awaitis a suspension point, exactly likeyieldin a coroutine; the single result is delivered through theFuturerather than a stream of yields. - Calls are eager. Calling an async fn runs its body up to the
first
await(or to completion if it never awaits) before returning theFuture. Start several async calls, thenawait/awaitAllthem, to run them concurrently. awaitworks everywhere. Inside an async fn it suspends cooperatively; at a non-async boundary (the top level, a callback) it blocks the caller and pumps the event loop until the future resolves.- Errors are values. Tzopilotl has no exceptions, so a fallible
async operation returns a
Futureof anOptionor a result enum; handle it withmatchafter theawait.