Async / Await and Fetch
async fn is sugar for fn*: calling an async function schedules its body
in the cooperative scheduler and immediately returns a Future<T>. await
suspends the caller until the Future is resolved.
At the top of a program, await internally calls block_on and drives the
scheduler synchronously — so you can use await directly at the module level.
Sequential await, Promise.all for parallelism, Promise.race, Promise.any, and Future cancellation.
// Feature: TypeScript-style async/await
// Syntax:
// async fn name() -> T { ... return T }
// let fut = name() // returns a Future<T>, body spawned
// let v = await fut // parks until fut settles, returns T
//
// Calling an `async fn` schedules its body on the cooperative
// scheduler and returns a Future immediately. `await` parks the
// caller until the Future settles. Top-level `await` falls back to
// `block_on(fut)` and drives the scheduler synchronously.
//
// For lazy producer/iterator semantics use `fn*` (generator) instead.
// `async fn` is for tasks: I/O simulation, parallel work, fan-out/in.
use std::Promise
async fn fetch(url: str) -> str {
sleep 30ms
return "GET {url} -> 200 OK"
}
// Sequential await — feels like synchronous code:
print("=== sequential")
let r1 = await fetch("/a")
let r2 = await fetch("/b")
print(r1)
print(r2)
// Parallel via Promise.all — both bodies run concurrently:
print("=== parallel via Promise.all")
let pages = await Promise.all([
fetch("/x"),
fetch("/y"),
fetch("/z"),
])
print("len: {pages.len()}")
print(pages[0])
print(pages[1])
print(pages[2])
// Promise.race — first to settle wins:
async fn slow_response() -> str {
sleep 100ms
return "slow"
}
async fn fast_response() -> str {
sleep 20ms
return "fast"
}
print("=== Promise.race")
let winner = await Promise.race([slow_response(), fast_response()])
print("winner: {winner}")
// Promise.any — first fulfillment wins (rejections are tolerated
// unless every input rejects).
async fn might_fail(label: str, succeed: bool) -> str {
sleep 25ms
if !succeed { panic("rejected: {label}") }
return label
}
print("=== Promise.any (some reject)")
let first = await Promise.any([
might_fail("a", false),
might_fail("b", true),
might_fail("c", false),
])
print("first ok: {first}")
// Cancellation — `fut.cancel()` settles the Future to "cancelled".
// Awaiters see a rejection; cooperative bodies can register cleanup
// via `fut.on_cancel(cb)` when they want it.
print("=== cancellation")
let f = slow_response()
f.cancel()
print("done after cancel: {f.is_done()}")
// Promise.resolve / Promise.reject — wrap synchronous values:
print("=== Promise.resolve / .reject")
let imm = await Promise.resolve("hello")
print("immediate: {imm}")
For communication with HTTP servers, Zolo offers a Node.js-style fetch API.
The return value is a Future<Response>; the body can be read with .text(),
.json(), or .bytes() — all asynchronous.
Simple GET, POST with JSON, parallel fan-out via Promise.all, and cancellation via AbortController.
// Feature: Node.js-style fetch
//
// `fetch(url, options?)` returns a Future<Response>. Use `await` to
// settle it. Response carries `.status`, `.ok`, `.headers` (case-
// insensitive) and async body readers `.text()`, `.json()`, `.bytes()`.
//
// HTTPS works via rustls. No HTTPS-specific configuration needed.
use std::json
use std::Promise
// ── Simple GET ────────────────────────────────────────────────────
print("=== GET")
let res = await fetch("http://httpbin.org/get")
print("status: {res.status}")
print("ok: {res.ok}")
let ctype = res.headers.get("content-type")
print("ctype: {ctype}")
let body = await res.text()
print("body length: {body.len()}")
// ── POST with JSON body ───────────────────────────────────────────
print("=== POST JSON")
let post = await fetch("http://httpbin.org/post", #{
method: "POST",
headers: #{"Content-Type": "application/json"},
body: json.encode(#{name: "alice", age: 30}),
})
let data = await post.json()
print("server saw: {data.json.name}")
// ── Parallel fan-out via Promise.all ──────────────────────────────
print("=== parallel")
let pages = await Promise.all([
fetch("http://httpbin.org/delay/1"),
fetch("http://httpbin.org/delay/1"),
fetch("http://httpbin.org/delay/1"),
])
print("got {pages.len()} responses concurrently")
// ── Cancellation via AbortController ──────────────────────────────
print("=== abort")
let ctrl = AbortController.new()
let p = fetch("http://httpbin.org/delay/10", #{signal: ctrl.signal})
setTimeout(|| { ctrl.abort() }, 100)
let ok, err = pcall(|| { await p })
print("aborted: ok={ok} err={err}")
Tip: use
async fnwhen the semantics are those of a task (I/O, parallel work, fan-out). For lazy sequences preferfn*(generator).
See also