Skip to content

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.

03-async-await.zolo
Playground
// 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.

04-fetch.zolo
Playground
// 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 fn when the semantics are those of a task (I/O, parallel work, fan-out). For lazy sequences prefer fn* (generator).

enespt-br