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 await on the same future throws INVALID_OPERAND_TYPE (“Future not found or already awaited”).
  • Awaiting consumes the future: a second await on the same future throws. await once, then share the resulting value (copy it out first if several consumers need it).
  • There is no cancel for spawned (or deferred) futures — awaiting only waits. The only cancellable primitive is a repeating std.time.timer handle’s cancel().
  • race returns a future for the winner — see Race.

See also

  • Overview — fiber yield points
  • Sleep — how sleep yields while tasks run
  • Patterns — fan-out and aggregation
  • Errors — errors are stored on the future and re-thrown by await