Patterns
Common shapes for concurrent Scrii code — fan-out, fan-in, and keeping servers alive.
Fan-out — spawn many, await all
The canonical pattern: build an array of futures, then await them.
var jobs = [
fn() { std.async.sleep(20); return "alpha" },
fn() { std.async.sleep(15); return "beta" },
fn() { std.async.sleep(10); return "gamma" },
]
var futures = []
for (job : jobs) { futures:insert(std.async.spawn(job)) }
var results = []
for (f : futures) { results:insert(std.async.await(f)) }
print(results) // ["alpha", "beta", "gamma"] — order preserved by await order
Preserve input order by awaiting in the same order you spawned. For the fastest-first order, await as they complete or use Race.
Parallel HTTP fan-out
The same shape works for I/O — all fetches overlap:
var urls = ["http://127.0.0.1:8200/a", "http://127.0.0.1:8200/b", "http://127.0.0.1:8200/c"]
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)) } // ~1× slowest, not 3× sum
See also Spawn & Await for the 900 ms → 300 ms timing demo.
Keep-alive — servers and timers
A script that starts a server and then exits kills the server. The fix is to sleep or await:
// server
var srv = std.net.tcp_server()
srv.on_receive(fn(id, data) { srv.send(id, "echo: " + data) })
srv.listen(8999)
std.async.sleep(5000) // keep alive 5 s
srv.stop()
// repeating timer
var t = std.time.timer({callback: fn() { print("tick") }, duration: 1000, repeat: true})
std.async.sleep(3500)
t.cancel() // handle from std.time.timer
For one-shot timers, await the future instead:
var f = std.time.timer({callback: fn() { return 42 }, duration: 100, repeat: false})
print(std.async.await(f)) // 42
See std.time for handle vs future.
Shared state — coordinate via futures
Engine state is mutex-guarded, but script globals mutated by multiple fibers are not atomic:
var counter = 0
var fs = []
for (i : std.seq.range(10)) {
fs:insert(std.async.spawn(fn() { counter++ }))
}
for (f : fs) { std.async.await(f) }
print(counter) // often 10, but not guaranteed — races on counter++
Fix: aggregate via futures instead of shared mutation:
var fs2 = []
for (i : std.seq.range(10)) { fs2:insert(std.async.spawn(fn() { return 1 })) }
var sum = 0
for (f : fs2) { sum += std.async.await(f) }
print(sum) // reliably 10