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
asyncfunction always returns aPromise<T>. Write the return type that way. awaitwaits for a promise and gives you its value.awaitworks insideasyncfunctions 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
awaitinsideseriesorparallel. They do the waiting. - They are statements, not expressions.
let x = parallel { ... }is an error. - They need an async context: an
async,seriesorparallelfunction, or the top level of the entry file. - No
paralleldirectly insideparallel. Use one block. - No
returnout of aparallelstatement. Each statement runs as its own task. - If a statement in a
parallelblock throws, the block waits for the others to finish, then throws the first error.