Async Programming
Tova has first-class support for asynchronous programming with async and await keywords. Since Tova compiles to JavaScript, async operations map directly to JavaScript promises with zero overhead.
Async Functions
Declare an asynchronous function by prefixing fn with async:
async fn fetch_data(url) {
response = await fetch(url)
data = await response.json()
data
}An async fn always returns a promise. Within the function body, you can use await to pause execution until a promise resolves.
Await
The await keyword pauses the async function until the awaited promise resolves, then returns the result:
async fn get_user(id) {
response = await fetch("/api/users/{id}")
user = await response.json()
user
}You can await any expression that returns a promise:
async fn load_config() {
response = await fetch("/config.json")
config = await response.json()
print("Loaded config: {config.app_name}")
config
}Sequential vs Parallel Awaits
Sequential
By default, await calls run one after another:
async fn load_page_data() {
user = await fetch_user(current_user_id)
posts = await fetch_posts(user.id)
comments = await fetch_comments(posts[0].id)
// Each await waits for the previous one to complete
{ user, posts, comments }
}Parallel
To run multiple async operations concurrently, use Promise.all():
async fn load_dashboard() {
results = await Promise.all([
fetch_user(user_id),
fetch_notifications(),
fetch_stats()
])
let [user, notifications, stats] = results
{ user, notifications, stats }
}This starts all three requests simultaneously and waits for all of them to finish, which is much faster than sequential awaits when the operations are independent.
Async Lambdas
Anonymous functions can be async too:
handler = async fn(request) {
data = await process(request.body)
{ status: 200, body: data }
}items = ["url1", "url2", "url3"]
results = await Promise.all(
items.map(async fn(url) {
response = await fetch(url)
await response.json()
})
)Error Handling in Async Code
With Try/Catch
Since await can reject (throw), use try/catch for JavaScript interop:
async fn safe_fetch(url) {
try {
response = await fetch(url)
if response.ok {
Ok(await response.json())
} else {
Err("HTTP {response.status}")
}
} catch err {
Err("Network error: {err.message}")
}
}With Result
Wrap async results in Result for idiomatic Tova error handling:
async fn fetch_user(id) -> Result<User, String> {
try {
response = await fetch("/api/users/{id}")
if response.ok {
data = await response.json()
Ok(data)
} else {
Err("User not found")
}
} catch err {
Err("Request failed: {err.message}")
}
}
async fn display_user(id) {
match await fetch_user(id) {
Ok(user) => print("Hello, {user.name}!")
Err(error) => print("Error: {error}")
}
}Using with the Fetch API
The browser's fetch API is the most common use case for async in Tova:
GET Request
async fn get_todos() {
response = await fetch("/api/todos")
todos = await response.json()
todos
}POST Request
async fn create_todo(title) {
response = await fetch("/api/todos", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title: title, completed: false })
})
created = await response.json()
created
}PUT Request
async fn update_todo(id, updates) {
response = await fetch("/api/todos/{id}", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(updates)
})
await response.json()
}DELETE Request
async fn delete_todo(id) {
await fetch("/api/todos/{id}", {
method: "DELETE"
})
}Async in Client Blocks
In Tova's full-stack architecture, async is commonly used in browser blocks with effect:
browser {
state users = []
state loading = true
effect {
data = await fetch("/api/users")
users_data = await data.json()
users = users_data
loading = false
}
component UserList {
if loading {
<p>"Loading..."</p>
} else {
<ul>
for user in users {
<li>"{user.name}"</li>
}
</ul>
}
}
}Practical Tips
Prefer parallel when possible. If two async operations do not depend on each other, run them with Promise.all() instead of awaiting them sequentially. This can dramatically improve performance:
// Slow: sequential (total time = time_a + time_b)
a = await fetch_a()
b = await fetch_b()
// Fast: parallel (total time = max(time_a, time_b))
results = await Promise.all([fetch_a(), fetch_b()])
let [a, b] = resultsAlways handle errors. Every await can fail. Wrap fetch calls in try/catch or return Result types to handle failures gracefully.
Keep async boundaries clear. An async fn returns a promise, which means callers must await it. Be aware of which functions in your codebase are async and which are synchronous.
Use async lambdas for inline handlers:
button.on("click", async fn() {
data = await save_changes()
update_ui(data)
})