Skip to content

Async, series and parallel ​

Games wait on things all the time: files, network calls, a timer, an animation finishing. ThicketScript has the async and await you know from TypeScript, plus two blocks of its own, series and parallel, that say what you mean with less ceremony.

async and await ​

ts
async function loadLevel(name: string): Promise<string> {
    await sleep(10);
    return "level " + name;
}

const level: string = await loadLevel("1-1");
console.log(level);
  • An async function always returns a Promise<T>. Write the return type that way.
  • await waits for a promise and gives you its value.
  • await works inside async functions and at the top level of the entry file.
  • sleep(ms) is built in.

A rejected promise throws at the await, so try and catch work as you expect:

ts
async function risky(): Promise<int> {
    throw new Error("network down");
}

try {
    const n: int = await risky();
    console.log(n);
} catch (err) {
    console.log("failed: " + err.message);
}

A promise that rejects with nobody listening is an unhandled rejection. thicket run reports it and exits with code 1.

Promise helpers ​

Promise.all, Promise.race, Promise.allSettled and Promise.any work as in TypeScript, along with .then, .catch and .finally.

ts
async function fetchScore(id: int): Promise<int> {
    await sleep(5);
    return id * 10;
}

const scores: Array<int> = await Promise.all([fetchScore(1), fetchScore(2)]);
console.log(scores.join(", "));

Timers ​

ts
const handle: int = setTimeout((): void => {
    console.log("too late");
}, 5000);
clearTimeout(handle);

setTimeout((): void => {
    console.log("fired");
}, 10);

setInterval and clearInterval repeat. In a window app, the App lifecycle callbacks are usually the better tool for per-frame work.

series: one after another ​

Inside a series block, every call that returns a promise is awaited for you. No await on each line:

ts
async function step(name: string): Promise<string> {
    await sleep(5);
    return name + " done";
}

series {
    const a: string = step("load map");
    const b: string = step("spawn enemies");
    console.log(a);
    console.log(b);
}

Calls that are not async cost nothing extra. A call typed Promise<int> gives you an int directly.

series also works on a function or method, which makes the whole body implicitly awaited:

ts
async function fade(name: string): Promise<void> {
    await sleep(5);
    console.log("faded " + name);
}

series function intro(): void {
    fade("logo");
    fade("title");
    console.log("intro finished");
}

await intro();

To start something without waiting for it, put void in front: void playMusic("town.ogg");.

parallel: all at once ​

Every statement in a parallel block starts at the same time. The block ends when all of them have finished, like Promise.all:

ts
async function loadAsset(name: string, ms: int): Promise<string> {
    await sleep(ms);
    return name;
}

parallel {
    let texture: string = loadAsset("tiles.png", 20);
    let music: string = loadAsset("theme.ogg", 10);
}
console.log(texture, music);

Variables declared with let in a parallel block can be read after it.

Mixing them ​

Nest parallel inside series to load some things in order and others together:

ts
async function load(name: string): Promise<string> {
    await sleep(5);
    return name;
}

series {
    const config: string = load("config");
    parallel {
        let tiles: string = load("tiles");
        let sounds: string = load("sounds");
    }
    console.log(config, tiles, sounds);
}

series and parallel also work on loops and if: series for (const item of items) { ... } handles one item at a time, and parallel for (const item of items) { ... } starts them all at once.

The rules ​

  • No await inside series or parallel. They do the waiting.
  • They are statements, not expressions. let x = parallel { ... } is an error.
  • They need an async context: an async, series or parallel function, or the top level of the entry file.
  • No parallel directly inside parallel. Use one block.
  • No return out of a parallel statement. Each statement runs as its own task.
  • If a statement in a parallel block throws, the block waits for the others to finish, then throws the first error.