Async functions and tasks
An async function gives a Task at once and does its work while the
program goes on; await waits for a task and gives its Result, which ?
passes on as for any other:
import "std/async"
async func load_both(str a, str b) Result[str] do
Task[str] first = fetch(a) // both start now
Task[str] second = fetch(b)
str text = await first? + await second?
return Result.OK(text)
end
match await load_both("a.txt", "b.txt") do
when OK(text) then
print(text)
when ERROR(e) then
eprint(e)
endCalling an async function starts it: it runs until it awaits a task that
has not settled, and then the caller goes on with the Task it was given.
The task settles when the function returns, with what it returns, or when
a ? in it passes an error on, which fails the task whatever the function
returns:
| Declared result | Task | Settles with |
|---|---|---|
Result[T] |
Task[T] |
the value of OK, or the error |
T |
Task[T] |
the value |
| nothing | Task[bool] |
true |
await task gives a Result[T]: OK with the value, or ERROR with the
error the task failed with. It can be used in an async function's own
statements, where the function stops until the task settles while other
code runs, and in the program's body, which runs the event
loop meanwhile; awaiting a task that nothing on
the loop will settle is then a runtime error. await binds tighter than
?, so await load(path)? is the value. It cannot be used in a function
written as an expression, in deferred code, or in a function that is not
async.
A method can be async, as in async func save(self) Result[bool]; a
function of a type that does not take self cannot. Async functions can be
generic and pub, and are declared only at the top level or in a type.
Task[T] is a type that every program has. Task() makes one that is
pending until something settles it, and done says whether it has; the
module std/async settles tasks by hand and
combines them, with timers, timeouts, and cancellation:
Task[str] answer = Task()
after(100, func() do
resolve(answer, "ready")
end)
print(await answer?)Each task's code runs on a stack of its own, so it can stop at an await
in the middle of any calls and go on later: deferred code runs when the
async function ends, a runtime error shows the calls that led to it through
the tasks that awaited one another, and the collector sees the values of
the tasks that wait. Waiting tasks go on as callbacks of the event loop,
so a program that neither awaits nor runs the loop ends without them. Each
stack has 1 MB, of which deeply recursive code in a task may use 896 KB
before the stack guard stops it. On POSIX, async functions run on x86-64
and AArch64 processors.