Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/elfrask/cls/llms.txt

Use this file to discover all available pages before exploring further.

CLS has first-class support for asynchronous programming through a coroutine model built on top of the Promise and Pollable abstractions in the runtime. An async function is a function whose body is wrapped in a coroutine: calling it does not execute the body immediately — it returns a Promise. The body only runs when that promise is polled, which happens when you await it. The async module provides utility functions for composing and scheduling multiple promises.

Import

import "async" as async;

Core Language Keywords

async function

Declaring a function with the async modifier causes the interpreter to wrap its body in a CoroutineTask. Calling the function returns a Value::Promise without running any of the body yet.
async function fetchData(url: String) -> String {
    # body runs only when awaited
    return "result for $url";
};

var promise = fetchData("https://api.example.com");
# promise is a Promise — body has not run yet

await

The await expression polls a Promise to completion. It must be used inside an async function. The expression evaluates to the resolved value of the promise.
async function main() -> int {
    var result = await fetchData("https://api.example.com");
    print(result);   # result for https://api.example.com
    return 0;
};
Using await outside an async function is a runtime error. The promise will be polled but the enclosing execution context does not have a scheduler to suspend on Pending, so Pending simply returns Void.

async Module Functions

async.delay(ms: int) -> Promise

Returns a Promise that resolves to void after the given number of milliseconds. Internally it spawns a background thread that sleeps for ms ms, and the promise becomes ready when that thread joins.
import "async" as async;

async function example() {
    print("start");
    await async.delay(500);
    print("500ms later");
};

async.all(promises: Array) -> Promise

Takes an array of Promise values and returns a new Promise that resolves to an Array containing all of the results, in the same order as the input. The composed promise only resolves once every input promise has resolved. If any promise rejects, async.all rejects immediately.
import "async" as async;

async function loadAll() -> Array {
    var p1 = fetchUser(1);
    var p2 = fetchUser(2);
    var p3 = fetchUser(3);

    var results = await async.all([p1, p2, p3]);
    return results;   # [user1, user2, user3]
};

async.race(promises: Array) -> Promise

Takes an array of Promise values and returns a new Promise that resolves with the result of whichever input promise resolves first. Remaining promises are not awaited.
import "async" as async;

async function fastest() -> String {
    var p1 = fetchFromServerA();
    var p2 = fetchFromServerB();

    var winner = await async.race([p1, p2]);
    return winner;
};

Complete Examples

Simple async/await

import "async" as async;

async function greet(name: String) -> String {
    await async.delay(100);
    return "Hello, $name!";
};

async function main() -> int {
    var msg = await greet("CLS");
    print(msg);   # Hello, CLS!
    return 0;
};

await main();

Chaining async calls

import "async" as async;

async function step1() -> int {
    await async.delay(50);
    return 1;
};

async function step2(prev: int) -> int {
    await async.delay(50);
    return prev + 1;
};

async function pipeline() -> int {
    var a = await step1();
    var b = await step2(a);
    return b;
};

var result = await pipeline();
print(result);   # 2

Waiting for multiple operations with async.all

import "async" as async;

async function slowAdd(a: int, b: int) -> int {
    await async.delay(200);
    return a + b;
};

async function main() -> int {
    var results = await async.all([
        slowAdd(1, 2),
        slowAdd(3, 4),
        slowAdd(5, 6),
    ]);

    for each r in (results) {
        print(r);
    };
    # 3
    # 7
    # 11
    return 0;
};

await main();

First-to-resolve with async.race

import "async" as async;

async function slow() -> String {
    await async.delay(1000);
    return "slow";
};

async function fast() -> String {
    await async.delay(100);
    return "fast";
};

async function main() -> int {
    var winner = await async.race([slow(), fast()]);
    print(winner);   # fast
    return 0;
};

await main();

How Coroutines Work Internally

When the interpreter encounters an async function call, it wraps the function body and its bound arguments in a CoroutineTask (a Pollable). That task is boxed inside a Promise. No code in the body runs at this point. When await is evaluated:
  1. The interpreter calls Promise::poll(self) on the wrapped Pollable.
  2. CoroutineTask::poll runs the async body synchronously to its next yield point.
  3. If the body completes, PollState::Ready(value) is returned and await resolves to that value.
  4. If the body is still waiting on a nested delay or I/O, PollState::Pending is returned and await yields Void in the current scheduler tick.
async.delay uses a real OS thread sleeping in the background. async.all polls promises sequentially; async.race polls them in order and returns the first Ready result.
The CLS scheduler is currently cooperative and single-threaded. True parallel execution of async.all branches does not happen — each promise is polled in sequence within a single tick. For I/O-bound concurrency, use async.race or structure your code so that the delay work happens in background threads (as async.delay already does).

Build docs developers (and LLMs) love