UPdate.
This commit is contained in:
@@ -0,0 +1,312 @@
|
||||
{# retoor <retoor@molodetz.nl> #}
|
||||
{% extends 'page.html' %}
|
||||
|
||||
{% set page_title = "Async Programming" %}
|
||||
{% set breadcrumb = [{"url": "howto/index.html", "title": "How-To Guides"}, {"title": "Async Operations"}] %}
|
||||
{% set prev_page = {"url": "howto/file-operations.html", "title": "File Operations"} %}
|
||||
{% set next_page = {"url": "howto/error-handling.html", "title": "Error Handling"} %}
|
||||
|
||||
{% block article %}
|
||||
<h1>Async Programming</h1>
|
||||
|
||||
<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 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
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// 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"])
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
System.print("Starting...")
|
||||
Timer.sleep(1000) // Wait 1 second
|
||||
System.print("Done!")</code></pre>
|
||||
|
||||
<h2>Async with Error Handling</h2>
|
||||
<pre><code>import "web" for Client
|
||||
import "scheduler" for Scheduler, Future
|
||||
import "json" for Json
|
||||
|
||||
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}
|
||||
}
|
||||
|
||||
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>Retry with Backoff</h2>
|
||||
<pre><code>import "web" for Client
|
||||
import "scheduler" for Scheduler, Future
|
||||
import "timer" for Timer
|
||||
|
||||
var fetchWithRetry = async { |url, maxRetries|
|
||||
var attempt = 0
|
||||
var delay = 1000
|
||||
|
||||
while (attempt < maxRetries) {
|
||||
var fiber = Fiber.new { Client.get(url) }
|
||||
var result = fiber.try()
|
||||
|
||||
if (!fiber.error && result["status"] == 200) {
|
||||
return result
|
||||
}
|
||||
|
||||
attempt = attempt + 1
|
||||
System.print("Attempt %(attempt) failed, retrying...")
|
||||
Timer.sleep(delay)
|
||||
delay = delay * 2
|
||||
}
|
||||
|
||||
Fiber.abort("All %(maxRetries) attempts failed")
|
||||
}
|
||||
|
||||
var response = await fetchWithRetry("https://api.example.com/data", 3)
|
||||
System.print("Success: %(response["status"])")</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
|
||||
|
||||
var pollUntilReady = async { |url, maxAttempts|
|
||||
var attempts = 0
|
||||
|
||||
while (attempts < maxAttempts) {
|
||||
attempts = attempts + 1
|
||||
System.print("Checking status (attempt %(attempts))...")
|
||||
|
||||
var response = Client.get(url)
|
||||
var data = Json.parse(response["body"])
|
||||
|
||||
if (data["status"] == "ready") {
|
||||
return data
|
||||
}
|
||||
|
||||
Timer.sleep(2000)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
var result = await pollUntilReady("https://api.example.com/job/123", 10)
|
||||
if (result) {
|
||||
System.print("Job completed: %(result)")
|
||||
} else {
|
||||
System.print("Timed out waiting for job")
|
||||
}</code></pre>
|
||||
|
||||
<h2>Rate Limiting</h2>
|
||||
<pre><code>import "web" for Client
|
||||
import "scheduler" for Scheduler, Future
|
||||
import "timer" for Timer
|
||||
|
||||
var rateLimitedFetch = async { |urls, delayMs|
|
||||
var results = []
|
||||
|
||||
for (url in urls) {
|
||||
var response = Client.get(url)
|
||||
results.add(response)
|
||||
Timer.sleep(delayMs)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
var urls = [
|
||||
"https://api.example.com/1",
|
||||
"https://api.example.com/2",
|
||||
"https://api.example.com/3"
|
||||
]
|
||||
|
||||
var responses = await rateLimitedFetch(urls, 500)
|
||||
for (r in responses) {
|
||||
System.print(r["status"])
|
||||
}</code></pre>
|
||||
|
||||
<h2>Graceful Shutdown</h2>
|
||||
<pre><code>import "signal" for Signal
|
||||
import "timer" for Timer
|
||||
|
||||
var running = true
|
||||
|
||||
Signal.handle("SIGINT", Fn.new {
|
||||
System.print("\nShutting down gracefully...")
|
||||
running = false
|
||||
})
|
||||
|
||||
System.print("Press Ctrl+C to stop")
|
||||
|
||||
while (running) {
|
||||
System.print("Working...")
|
||||
Timer.sleep(1000)
|
||||
}
|
||||
|
||||
System.print("Cleanup complete, exiting.")</code></pre>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<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 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>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user