Async / await
Vow ships a cooperative async runtime in 0.1.0. Use async fn + await for I/O and blocking work; call async code from sync route handlers with block_on.
HTTP handlers in vow-web-server stay fn(Request) -> Response. Put async work in helpers, then block_on(helper) inside the handler — same pattern as Express calling an async function from a sync wrapper.
Quick example — Postgres + HTTP
Section titled “Quick example — Postgres + HTTP”From the hello-app demo:
async fn todo_count() -> int { let rows = await db_query(g_handle, "SELECT id FROM todos")?; let doc = json.parse(rows)?; return json_len(doc)?;}
fn api_health(_req: request.Request) -> response.Response { if not db_ready() { return vws.send(503, "database not ready"); } return vws.json(json.stringify({ ok: true, todos: block_on(todo_count), }));}cd hello-appdocker compose up -dvow run src/main.vow -- --grant db:postgres://localhost:5433/todos --grant net:curl http://127.0.0.1:8787/api/healthSyntax
Section titled “Syntax”| Form | Role |
|---|---|
async fn name() -> T |
Async function — body may use await |
await expr |
Suspend until the await target completes |
expr? |
Propagate Result / Option errors (works inside async fn) |
block_on(async_fn) |
Run async fn to completion from sync code |
block_on(async_fn, arg1, …) |
Same, passing parameters to the async fn |
Return types for block_on are inferred from the async function (int, String, Result<…>, etc.).
Valid await targets
Section titled “Valid await targets”| Await | Use case |
|---|---|
await db_query(handle, sql)? |
Database (offloaded to worker thread) |
await read_file_async(path) |
Filesystem read |
await async_sleep(ms) |
Timers / scheduling |
await spawn_blocking(sync_fn) |
CPU or custom blocking fn() -> int |
await other_async_fn() |
Compose no-arg async helpers |
await async_spawn(child) |
Fire-and-forget child task |
Database init with caps
Section titled “Database init with caps”Async functions that call open_db() or other needs Db helpers must declare needs Db. The capability is passed from the sync caller via with db { block_on(init_db_work) } — the compiler stores the cap in the async frame before the worker runs.
fn open_db() -> Result<int64, int> needs Db { return match vpg.connect(db_url()) { Ok(c) => Ok(c.handle), Err(e) => Err(e.code), };}
async fn init_db_work() -> int needs Db { let h = open_db()?; let rows = await db_query(h, "SELECT id, title, done FROM todos ORDER BY id")?; g_handle = h; g_db_ok = 1; return 0;}
fn init_db(caps: Caps) -> int { let db = caps.db?; // Option? → Db return with db { block_on(init_db_work) }; // propagates Db into async frame}Call init_db(caps) from main before starting the HTTP server.
Create route with ? + block_on
Section titled “Create route with ? + block_on”Return a JSON String from the async helper; check for empty result in the sync handler:
async fn insert_todo_work(body: String) -> String { let _doc = json.parse(body)?; let rows = await db_query(g_handle, sql)?; let parsed = json.parse(rows)?; let row = json_at(parsed, 0)?; return json.stringify({ ok: true, id: json.get_int(row, "id", 0), title: title, done: false });}
fn create_todo(req: request.Request) -> response.Response { let body = str_trim(vws.body(req, 65536)); let payload = block_on(insert_todo_work, body); if len(payload) == 0 { return vws.send(503, "database error"); } return vws.json(201, payload);}Custom blocking work
Section titled “Custom blocking work”fn heavy_compute() -> int { return 42;}
async fn load() -> int { let a = await child_async(); let b = await spawn_blocking(heavy_compute); return a + b;}
fn main(_caps: Caps) -> int { return block_on(load);}See tests/lang/test_async_general.sh (expects exit code 49).
Files and sleep
Section titled “Files and sleep”async fn read_one(path: String) -> int needs FsRead { return match await read_file_async(path) { Ok(_s) => 1, Err(_e) => 0, };}
async fn wait() -> int needs Clock { let _ = await async_sleep(20); return 1;}Grant caps as usual: --grant fs-read:/path, --grant clock:.
vow-web-server pattern
Section titled “vow-web-server pattern”- Sync handler — parses request, returns
response.Response - Async helper —
await db_query,await read_file_async, etc. block_on(helper)— bridges sync HTTP to async I/O
Handlers are not async fn themselves. The accept loop is single-threaded; async yields keep the process responsive while DB/file work runs on thread-pool workers.
Tests & examples
Section titled “Tests & examples”| Example | Command |
|---|---|
tests/lang/test_async_general.sh |
child + spawn_blocking + multi-await locals |
tests/lang/test_async_db.sh |
await db_query + ? |
tests/lang/test_async_needs_cap.sh |
needs Db propagated through block_on |
tests/lang/test_option_block_on.sh |
caps.db? + with db { block_on(...) } |
tests/lang/test_async_block_on_str.sh |
block_on returning String |
tests/lang/async_join.vow |
async_spawn + join |
tests/lang/async_scheduler.vow |
async_sleep |
hello-app/src/main.vow |
Full-stack Postgres + HTTP |
Limitations (0.1.0)
Section titled “Limitations (0.1.0)”- Not M:N — cooperative scheduler + pthread offload, not a full event-loop runtime
- No network async await — HTTP client I/O is still sync
spawn_blocking— no-argfn() -> intonly today- vow-web-server — handlers are sync; use
block_oninside them
See Concurrency for OS threads and Limitations.