LEVIATHAN v962456e · 962456eee1

Library

await — waiting for a promise

await unwraps a promise to its value, suspending only the current task while other work keeps running.

since 0.1.0-alpha.1linuxwindowswasm

Description

await expr waits for expr and evaluates to its value. When expr is a Promise<T>, the result has type T. There is no async keyword and no function coloring: any function may await, and a function that returns a Promise<T> is simply the asynchronous form of a function that returns T.

What happens at an await depends on the state of the promise.

  • If expr is not a promise, await yields it unchanged.
  • If the promise is already ready, await carries on immediately. It does not give any other work a chance to run; it is as cheap as reading the value.
  • Otherwise the current task is suspended. Everything else keeps running while it waits: timers fire, sockets deliver data, and other tasks resume. When the promise is settled, the task continues after the await with the value. See lang.tasks for what a task is.

Timers resolve promises; await parks until each one is ready

Promise<int> after(int ms, int value) {
    Promise<int> p = Promise();
    std::sysTimerStart(ms, 0, (n) => {
        console.writeln("timer " + ms.toString() + "ms fired");
        p.resolve(value);
    });
    return p;
}

void run() {
    // created together, due at 30, 20 and 10 ms
    Promise<int> a = after(30, 1);
    Promise<int> b = after(20, 2);
    Promise<int> c = after(10, 3);
    // awaited in creation order; the timers still fire in due-time order
    int x = await a;
    console.writeln("got a = " + x.toString());
    int y = await b;
    int z = await c;
    console.writeln("got b = " + y.toString() + ", c = " + z.toString());
    console.writeln("total " + (x + y + z).toString());
}
run();
timer 10ms fired
timer 20ms fired
timer 30ms fired
got a = 1
got b = 2, c = 3
total 6

Rules

  • await expr has the value of the promise's content, or expr itself when expr is not a promise. A Promise<void> yields no usable value.
  • An await on a ready promise does not suspend. It never lets another task run. A loop of awaits on ready promises is plain computation, not cooperation: other work only gets a turn at the first await that really has to wait.
  • A suspended task resumes in completion order. Among the tasks of one thread that are ready to continue, they resume first-come first-served. Do not rely on any other order.
  • Anything can happen at an await. While a task is suspended, other tasks on the same thread run and may read or change shared state. await marks the places where that can happen.
  • Failure at an await. An await on a Worker whose body threw rethrows the failure there, where try/catch can handle it. If the event loop has no work left that could ever settle the promise, the await throws a RuntimeException with the message await: event loop drained with promise unresolved; it can be caught.
  • An exception thrown in a callback is never delivered to an unrelated await. A timer or socket callback runs as its own task, so an uncaught throw there ends the program through the usual uncaught-exception path (exit status 1).
  • await cannot be used inside comptime code. It is a compile error there.
  • then(callback) does not suspend anything; it runs the callback synchronously, at the point where the promise is resolved.

Examples

A ready promise does not suspend, so the pending timer callback cannot run until a real wait begins:

Ready awaits do not suspend

Promise<int> after(int ms, int value) {
    Promise<int> p = Promise();
    std::sysTimerStart(ms, 0, (n) => p.resolve(value));
    return p;
}

std::sysTimerStart(0, 0, (n) => console.writeln("timer callback ran"));

int a = await Promise(5);
console.writeln("ready await did not suspend: ${a}");

int b = await after(10, 6);
console.writeln("suspended await resumed: ${b}");
ready await did not suspend: 5
timer callback ran
suspended await resumed: 6

A promise that nothing can ever resolve is detected, not waited on forever:

Awaiting a promise nobody can resolve

Promise<int> p = Promise();
try {
    int x = await p;
    console.writeln("got ${x}");
} catch (IException e) {
    console.writeln("caught: ${e.message}");
}
caught: await: event loop drained with promise unresolved

A throw inside a timer callback is not delivered to the await that happens to be waiting. The program ends with Uncaught RuntimeException: boom and the await reports its own failure:

Promise<int> p = Promise();
std::sysTimerStart(5, 0, (n) => { throw RuntimeException("boom"); });
try { int x = await p; } catch (IException e) {
    console.writeln("caught: " + e.message);   // the drained-loop message, never "boom"
}
not run — ends the program with an uncaught exception

See also

  • Promise — A value that arrives later.
  • Timer — A source of ticks on the event loop.
  • Worker — The handle to a value that another worker is computing.
  • TaskGroup — A set of tasks that live and end together.