Spawn & Await
spawn starts a function now on the fiber pool and returns a future. await reads that future’s result, suspending the caller until it completes (and re-throwing any error as Async error: …).
Reference
std.async.spawn(fn[, args...]) # run fn(args...) now → future
std.async.await(future[, timeout_ms])
# → result or throws Async error;
# TIMEOUT (`Async timeout after Nms`) if the
# deadline elapses before completion
An optional timeout_ms bounds the wait: if the future hasn’t completed when
the deadline elapses, await throws TIMEOUT (Async timeout after Nms).
spawn copies its args by value (deep copy, like assignment). Use globals or & aliasing if the task must see live shared state (see Overview and Types — sharing with aliases).
Basic usage
var f = std.async.spawn(fn() { return 42 })
print(std.async.await(f)) # 42
var g = std.async.spawn(fn(a, b) { return a + b }, 10, 20)
print(std.async.await(g)) # 30
await may be called from the main script or from another fiber — it always yields until the target completes — or until the optional timeout_ms elapses.
Why spawn — overlapping work
The point is concurrency: spawned tasks run on the same worker pool and overlap while the caller waits. Compare serial vs concurrent for three 300 ms jobs:
var job = fn(name) { std.async.sleep(300); return name }
# serial — 900 ms (one finishes before the next starts)
var t0 = std.time.now()
job("a"); job("b"); job("c")
print("serial: ", std.time.elapsed(t0), "ms") # e.g. 902 ms
# concurrent — ~300 ms (all three overlap)
var t1 = std.time.now()
var f1 = std.async.spawn(job, "a")
var f2 = std.async.spawn(job, "b")
var f3 = std.async.spawn(job, "c")
var results = [std.async.await(f1), std.async.await(f2), std.async.await(f3)]
print("concurrent:", std.time.elapsed(t1), "ms", results) # e.g. 301 ms ["a","b","c"]
Use this whenever you have independent I/O — fetching several HTTP endpoints, processing several files, or running parallel std.time.timer work.
Full fan-out example with HTTP (all fetches overlapped):
var urls = ["https://a.example/1", "https://b.example/2", "https://c.example/3"]
var futures = []
for (u : urls) {
futures:insert(std.async.spawn(fn(u) {
return std.net.http_client().request({url: u}).body
}, u))
}
var bodies = []
for (f : futures) { bodies:insert(std.async.await(f)) } # parallel
Future lifecycle
- A future is ready after the fiber returns or throws. Awaiting consumes it: a second
awaiton the same future throwsINVALID_OPERAND_TYPE(“Future not found or already awaited”). - Awaiting consumes the future: a second
awaiton the same future throws.awaitonce, then share the resulting value (copy it out first if several consumers need it). - There is no
cancelforspawned (ordeferred) futures — awaiting only waits. The only cancellable primitive is a repeatingstd.time.timerhandle’scancel(). racereturns a future for the winner — see Race.