Chapter 8

Async/Await

Concurrent programming made simple

Why Async?

Asynchronous programming allows your code to perform multiple operations concurrently without blocking. This is essential for:

  • Network requests (API calls, database queries)
  • File I/O operations
  • Timer-based operations
  • Background processing

Defining Async Functions

Use the async keyword before func:

import stdlib time as t

// Define an async function
async func fetchUserData(userId) {
    print("Fetching user", userId, "...")
    t.sleep(100)  // Simulate network delay
    return "User_" + str(userId)
}

// Call with await
user = await fetchUserData(42)
print("Got:", user)

Await Expression

Use await to wait for an async function to complete:

import stdlib time as t

async func slowOperation() {
    t.sleep(200)
    return "Result after delay"
}

// Await pauses until the result is ready
result = await slowOperation()
print(result)  // "Result after delay"
Note

You can only use await at the top level or inside async functions.

Sequential vs Parallel

Sequential Execution

import stdlib time as t

async func fetch(name) {
    print("Start:", name)
    t.sleep(100)
    print("Done:", name)
    return name + " data"
}

// These run one after another (sequential)
startTime = t.clock()

result1 = await fetch("API 1")
result2 = await fetch("API 2")
result3 = await fetch("API 3")

elapsed = t.clock() - startTime
print("Total time:", elapsed, "seconds")
// Takes ~300ms (100ms × 3)

Practical Example: Data Pipeline

import stdlib time as t

// Simulate fetching from different sources
async func fetchFromDatabase(query) {
    print("DB: Executing query...")
    t.sleep(50)
    return ["row1", "row2", "row3"]
}

async func fetchFromAPI(endpoint) {
    print("API: Calling", endpoint)
    t.sleep(80)
    return "API response"
}

async func processData(data) {
    print("Processing", len(data), "items...")
    t.sleep(30)
    return "Processed: " + str(len(data)) + " items"
}

// Build a data pipeline
async func runPipeline() {
    // Step 1: Fetch raw data
    dbData = await fetchFromDatabase("SELECT * FROM users")
    print("Got DB data:", dbData)
    
    // Step 2: Enhance with API data
    apiData = await fetchFromAPI("/users/enrich")
    print("Got API data:", apiData)
    
    // Step 3: Process everything
    result = await processData(dbData)
    print("Final result:", result)
    
    return result
}

// Run the pipeline
finalResult = await runPipeline()
print("")
print("Pipeline complete:", finalResult)

Error Handling

import stdlib time as t

async func riskyOperation(shouldFail) {
    t.sleep(50)
    if (shouldFail) {
        return "ERROR"
    }
    return "SUCCESS"
}

// Check result and handle errors
result = await riskyOperation(false)

if (result == "ERROR") {
    print("Operation failed!")
} else {
    print("Operation succeeded:", result)
}

Task Queue Pattern

import stdlib time as t

// Task queue processing
taskQueue = ["Task A", "Task B", "Task C"]

async func processTask(task) {
    print("Processing:", task)
    t.sleep(50)
    return task + " done"
}

// Process all tasks
results = []
for (task in taskQueue) {
    result = await processTask(task)
    results.push(result)
}

print("All tasks complete:")
for (r in results) {
    print(" -", r)
}