This commit is contained in:
2026-01-25 19:02:02 +01:00
parent a830238d11
commit 1ee28a1644
92 changed files with 10955 additions and 621 deletions
+250 -234
View File
@@ -42,27 +42,38 @@
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/string.html">String</a></li>
<li><a href="../api/number.html">Num</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/argparse.html">argparse</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/dataset.html">dataset</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/fswatch.html">fswatch</a></li>
<li><a href="../api/html.html">html</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/markdown.html">markdown</a></li>
<li><a href="../api/math.html">math</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sysinfo.html">sysinfo</a></li>
<li><a href="../api/tempfile.html">tempfile</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/udp.html">udp</a></li>
<li><a href="../api/uuid.html">uuid</a></li>
<li><a href="../api/wdantic.html">wdantic</a></li>
<li><a href="../api/web.html">web</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
</ul>
</div>
<div class="section">
@@ -72,8 +83,9 @@
<li><a href="../tutorials/http-client.html">HTTP Client</a></li>
<li><a href="../tutorials/websocket-chat.html">WebSocket Chat</a></li>
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Template Rendering</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@@ -88,6 +100,19 @@
<li><a href="error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">
@@ -102,22 +127,155 @@
<article>
<h1>Async Programming</h1>
<p>Wren-CLI uses fibers for concurrent operations. Understanding fibers is key to writing efficient async code.</p>
<p>Wren-CLI provides <code>async</code> and <code>await</code> keywords for writing concurrent code. This guide covers the essential patterns for async programming.</p>
<h2>Create a Fiber</h2>
<pre><code>var fiber = Fiber.new {
System.print("Hello from fiber!")
<h2>Create an Async Function</h2>
<pre><code>import "scheduler" for Scheduler, Future
var getValue = async { 42 }
var result = await getValue()
System.print(result) // 42</code></pre>
<h2>Async Functions with Parameters</h2>
<pre><code>import "scheduler" for Scheduler, Future
var double = async { |x| x * 2 }
var add = async { |a, b| a + b }
System.print(await double(21)) // 42
System.print(await add(3, 4)) // 7</code></pre>
<h2>Direct Calling vs .call()</h2>
<p>There are two ways to invoke async functions:</p>
<ul>
<li><code>await fn(args)</code> — Direct call, waits immediately (sequential)</li>
<li><code>fn.call(args)</code> — Returns Future, starts without waiting (concurrent)</li>
</ul>
<pre><code>import "scheduler" for Scheduler, Future
var slow = async { |n|
Timer.sleep(100)
return n
}
fiber.call()</code></pre>
// SEQUENTIAL: Each call waits before the next starts
var a = await slow(1)
var b = await slow(2)
var c = await slow(3) // Total: ~300ms
<h2>Fibers with Return Values</h2>
<pre><code>var fiber = Fiber.new {
return 42
// CONCURRENT: All calls start at once, then wait for results
var f1 = slow.call(1)
var f2 = slow.call(2)
var f3 = slow.call(3)
var r1 = await f1
var r2 = await f2
var r3 = await f3 // Total: ~100ms</code></pre>
<h2>Sequential HTTP Requests</h2>
<pre><code>import "web" for Client
import "scheduler" for Scheduler, Future
import "json" for Json
var fetchJson = async { |url|
var response = Client.get(url)
return Json.parse(response["body"])
}
var result = fiber.call()
System.print("Result: %(result)") // 42</code></pre>
// Each request waits for the previous one
var user = await fetchJson("https://api.example.com/user/1")
var posts = await fetchJson("https://api.example.com/posts")
var comments = await fetchJson("https://api.example.com/comments")
System.print(user["name"])
System.print(posts.count)
System.print(comments.count)</code></pre>
<h2>Concurrent HTTP Requests</h2>
<pre><code>import "web" for Client
import "scheduler" for Scheduler, Future
import "json" for Json
var fetchJson = async { |url|
var response = Client.get(url)
return Json.parse(response["body"])
}
// Start all requests at once
var f1 = fetchJson.call("https://api.example.com/user/1")
var f2 = fetchJson.call("https://api.example.com/posts")
var f3 = fetchJson.call("https://api.example.com/comments")
// Wait for results (requests run in parallel)
var user = await f1
var posts = await f2
var comments = await f3
System.print(user["name"])
System.print(posts.count)
System.print(comments.count)</code></pre>
<h2>Batch Processing</h2>
<pre><code>import "web" for Client
import "scheduler" for Scheduler, Future
var urls = [
"https://api.example.com/1",
"https://api.example.com/2",
"https://api.example.com/3",
"https://api.example.com/4",
"https://api.example.com/5"
]
// Start all requests concurrently
var futures = []
for (url in urls) {
futures.add(async { Client.get(url) })
}
// Collect results
var responses = []
for (f in futures) {
responses.add(await f)
}
for (i in 0...urls.count) {
System.print("%(urls[i]): %(responses[i]["status"])")
}</code></pre>
<h2>Reusable Batch Fetcher</h2>
<pre><code>import "web" for Client
import "scheduler" for Scheduler, Future
import "json" for Json
var fetchJson = async { |url|
var response = Client.get(url)
return Json.parse(response["body"])
}
class BatchFetcher {
static getAll(urls) {
var futures = []
for (url in urls) {
futures.add(fetchJson.call(url))
}
var results = []
for (f in futures) {
results.add(await f)
}
return results
}
}
var urls = [
"https://api.example.com/users",
"https://api.example.com/posts",
"https://api.example.com/comments"
]
var results = BatchFetcher.getAll(urls)
for (result in results) {
System.print(result)</code></pre>
<h2>Sleep/Delay</h2>
<pre><code>import "timer" for Timer
@@ -126,133 +284,77 @@ System.print("Starting...")
Timer.sleep(1000) // Wait 1 second
System.print("Done!")</code></pre>
<h2>Sequential HTTP Requests</h2>
<pre><code>import "http" for Http
<h2>Async with Error Handling</h2>
<pre><code>import "web" for Client
import "scheduler" for Scheduler, Future
import "json" for Json
var r1 = Http.get("https://api.example.com/1")
var r2 = Http.get("https://api.example.com/2")
var r3 = Http.get("https://api.example.com/3")
System.print(r1.json)
System.print(r2.json)
System.print(r3.json)</code></pre>
<h2>Parallel HTTP Requests</h2>
<pre><code>import "http" for Http
var urls = [
"https://api.example.com/1",
"https://api.example.com/2",
"https://api.example.com/3"
]
var fibers = []
for (url in urls) {
fibers.add(Fiber.new { Http.get(url) })
var safeFetch = async { |url|
var fiber = Fiber.new {
var response = Client.get(url)
return Json.parse(response["body"])
}
var result = fiber.try()
if (fiber.error) {
return {"error": fiber.error}
}
return {"data": result}
}
for (fiber in fibers) {
fiber.call()
}
for (fiber in fibers) {
System.print(fiber.value)
var result = await safeFetch("https://api.example.com/data")
if (result["error"]) {
System.print("Error: %(result["error"])")
} else {
System.print("Data: %(result["data"])")
}</code></pre>
<h2>Run Task in Background</h2>
<pre><code>import "timer" for Timer
<h2>Retry with Backoff</h2>
<pre><code>import "web" for Client
import "scheduler" for Scheduler, Future
import "timer" for Timer
var backgroundTask = Fiber.new {
for (i in 1..5) {
System.print("Background: %(i)")
Timer.sleep(500)
}
}
var fetchWithRetry = async { |url, maxRetries|
var attempt = 0
var delay = 1000
backgroundTask.call()
while (attempt < maxRetries) {
var fiber = Fiber.new { Client.get(url) }
var result = fiber.try()
System.print("Main thread continues...")
Timer.sleep(3000)</code></pre>
<h2>Wait for Multiple Operations</h2>
<pre><code>import "http" for Http
var fetchAll = Fn.new { |urls|
var results = []
var fibers = []
for (url in urls) {
fibers.add(Fiber.new { Http.get(url) })
}
for (fiber in fibers) {
fiber.call()
}
for (fiber in fibers) {
results.add(fiber.value)
}
return results
}
var responses = fetchAll.call([
"https://api.example.com/a",
"https://api.example.com/b"
])
for (r in responses) {
System.print(r.statusCode)
}</code></pre>
<h2>Timeout Pattern</h2>
<pre><code>import "timer" for Timer
import "datetime" for DateTime
var withTimeout = Fn.new { |operation, timeoutMs|
var start = DateTime.now()
var result = null
var done = false
var workFiber = Fiber.new {
result = operation.call()
done = true
}
workFiber.call()
while (!done) {
var elapsed = (DateTime.now() - start).milliseconds
if (elapsed >= timeoutMs) {
Fiber.abort("Operation timed out")
if (!fiber.error && result["status"] == 200) {
return result
}
Timer.sleep(10)
attempt = attempt + 1
System.print("Attempt %(attempt) failed, retrying...")
Timer.sleep(delay)
delay = delay * 2
}
return result
Fiber.abort("All %(maxRetries) attempts failed")
}
var result = withTimeout.call(Fn.new {
Timer.sleep(500)
return "completed"
}, 1000)
var response = await fetchWithRetry("https://api.example.com/data", 3)
System.print("Success: %(response["status"])")</code></pre>
System.print(result)</code></pre>
<h2>Polling Pattern</h2>
<pre><code>import "web" for Client
import "scheduler" for Scheduler, Future
import "timer" for Timer
import "json" for Json
<h2>Polling Loop</h2>
<pre><code>import "timer" for Timer
import "http" for Http
var pollUntilReady = Fn.new { |url, maxAttempts|
var pollUntilReady = async { |url, maxAttempts|
var attempts = 0
while (attempts < maxAttempts) {
attempts = attempts + 1
System.print("Attempt %(attempts)...")
System.print("Checking status (attempt %(attempts))...")
var response = Http.get(url)
if (response.json["ready"]) {
return response.json
var response = Client.get(url)
var data = Json.parse(response["body"])
if (data["status"] == "ready") {
return data
}
Timer.sleep(2000)
@@ -261,22 +363,23 @@ var pollUntilReady = Fn.new { |url, maxAttempts|
return null
}
var result = pollUntilReady.call("https://api.example.com/status", 10)
var result = await pollUntilReady("https://api.example.com/job/123", 10)
if (result) {
System.print("Ready: %(result)")
System.print("Job completed: %(result)")
} else {
System.print("Timed out waiting for ready state")
System.print("Timed out waiting for job")
}</code></pre>
<h2>Rate Limiting</h2>
<pre><code>import "timer" for Timer
import "http" for Http
<pre><code>import "web" for Client
import "scheduler" for Scheduler, Future
import "timer" for Timer
var rateLimitedFetch = Fn.new { |urls, delayMs|
var rateLimitedFetch = async { |urls, delayMs|
var results = []
for (url in urls) {
var response = Http.get(url)
var response = Client.get(url)
results.add(response)
Timer.sleep(delayMs)
}
@@ -284,104 +387,17 @@ var rateLimitedFetch = Fn.new { |urls, delayMs|
return results
}
var responses = rateLimitedFetch.call([
var urls = [
"https://api.example.com/1",
"https://api.example.com/2",
"https://api.example.com/3"
], 1000)
]
var responses = await rateLimitedFetch(urls, 500)
for (r in responses) {
System.print(r.statusCode)
System.print(r["status"])
}</code></pre>
<h2>Producer/Consumer Pattern</h2>
<pre><code>import "timer" for Timer
var queue = []
var running = true
var producer = Fiber.new {
for (i in 1..10) {
queue.add(i)
System.print("Produced: %(i)")
Timer.sleep(200)
}
running = false
}
var consumer = Fiber.new {
while (running || queue.count > 0) {
if (queue.count > 0) {
var item = queue.removeAt(0)
System.print("Consumed: %(item)")
}
Timer.sleep(100)
}
}
producer.call()
consumer.call()
System.print("Done!")</code></pre>
<h2>Retry with Exponential Backoff</h2>
<pre><code>import "timer" for Timer
import "http" for Http
var retryWithBackoff = Fn.new { |operation, maxRetries|
var attempt = 0
var delay = 1000
while (attempt < maxRetries) {
var fiber = Fiber.new { operation.call() }
var result = fiber.try()
if (!fiber.error) {
return result
}
attempt = attempt + 1
System.print("Attempt %(attempt) failed, retrying in %(delay)ms...")
Timer.sleep(delay)
delay = delay * 2
}
Fiber.abort("All %(maxRetries) attempts failed")
}
var response = retryWithBackoff.call(Fn.new {
return Http.get("https://api.example.com/data")
}, 3)
System.print(response.json)</code></pre>
<h2>Concurrent WebSocket Handling</h2>
<pre><code>import "websocket" for WebSocket, WebSocketMessage
import "timer" for Timer
var ws = WebSocket.connect("ws://localhost:8080")
var receiver = Fiber.new {
while (true) {
var message = ws.receive()
if (message == null) break
if (message.opcode == WebSocketMessage.TEXT) {
System.print("Received: %(message.payload)")
}
}
}
var sender = Fiber.new {
for (i in 1..5) {
ws.send("Message %(i)")
Timer.sleep(1000)
}
ws.close()
}
receiver.call()
sender.call()</code></pre>
<h2>Graceful Shutdown</h2>
<pre><code>import "signal" for Signal
import "timer" for Timer
@@ -404,12 +420,12 @@ System.print("Cleanup complete, exiting.")</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>Wren fibers are cooperative, not preemptive. A fiber runs until it explicitly yields, calls an async operation, or completes. Long-running computations should periodically yield to allow other fibers to run.</p>
<p>Always import <code>Scheduler</code> and <code>Future</code> from the scheduler module when using <code>async</code> and <code>await</code>. The syntax requires these classes to be in scope.</p>
</div>
<div class="admonition tip">
<div class="admonition-title">See Also</div>
<p>For more on fibers, see the <a href="../language/fibers.html">Fibers language guide</a> and the <a href="../api/scheduler.html">Scheduler module reference</a>.</p>
<p>For more details, see the <a href="../api/scheduler.html">Scheduler API reference</a> and the <a href="../api/web.html">Web module</a> for HTTP client examples.</p>
</div>
</article>