std/async
Async functions give
tasks, and await waits for one. This module makes tasks of its own,
settled when a timer fires, when other tasks settle, or by hand, and settles
each one as soon as its outcome is known:
import "std/async"
async func fetch_all(str[] urls) Result[str[]] do
Task[str][] pages = []
for url in urls do
push(pages, with_timeout(download(url), 5000))
end
return Result.OK(await all_of(pages)?)
end| Function | Result |
|---|---|
resolve(task, value), reject(task, error) |
settles a task made with Task(), unless it has settled already |
on_settled(task, callback) |
runs callback with the task's Result once it settles: soon, if it has |
ready(value) |
a task that has settled with value |
delay(ms, token) |
a Task[bool] that settles with true after ms milliseconds; with a CancelToken, it fails with kind "cancelled" when the token is cancelled first |
and_then(task, f) |
a task with what f gives for the task's value; an error passes through |
recover(task, f) |
a task with the task's value, or what f gives for its error |
all_of(tasks) |
a task with the values of all the tasks, in their order, or the first error to come |
any_of(tasks) |
a task with the first value to come, or the last error when all of them fail |
race(tasks) |
a task that settles as the first of the tasks to settle does |
with_timeout(task, ms) |
a task that settles as task does, or fails with kind "timeout" after ms milliseconds |
cancellable(task, token) |
a task that settles as task does, or fails with kind "cancelled" when token is cancelled first |
A CancelToken says that work a program started is no longer wanted:
token.cancel(reason) cancels it once, cancelled and reason say whether
and why, and token.on_cancel(callback) runs callback with the reason
when it is cancelled, at once if it has been. Cancelling does not stop work
by itself: the work looks at the token, as delay and cancellable do.
CancelToken stop = CancelToken()
on_signal("interrupt", func() do
stop.cancel("interrupted")
end)
match await delay(60000, stop) do
when OK(_) then
print("a minute went by")
when ERROR(e) then
print(e.message) // interrupted
endCoordinating tasks
These help tasks work together without stepping on each other:
| Type or function | For |
|---|---|
Queue[T] |
values passed from tasks that make them to tasks that use them, in order: q.put(value) and q.take() give tasks; with capacity above 0, put waits while the queue is full, so a producer cannot run ahead of its consumers; q.close() makes waiting and later calls fail with kind "closed", and q.size() says how many values wait |
Semaphore(permits) |
at most permits tasks at once: s.acquire() gives a task that settles once a permit is held, in the order asked, and s.release() gives it back |
Event() |
something that has happened or not: e.wait() settles once e.set() is called, at once after it, and e.clear() makes later waits wait again |
debounce(ms, f) |
a function that runs f once calls to it stop, ms milliseconds after the last |
throttle(ms, f) |
a function that runs f at most once every ms milliseconds: the first call at once, and any during the wait once at its end |
rate_limiter(per_second, burst) |
a RateLimiter, a bucket of burst tokens that refills at per_second: l.take() gives a task that settles once a token is taken, in the order asked, and l.try_take() takes one only if it is free |
retry(attempt, options) |
runs attempt, a function that gives a task, again after each failure as a RetryOptions says, and gives the first value or the last error |
RetryOptions has the number of attempts in all (5), the wait after the
first failure, first_delay_ms (100), the factor each later wait grows by
(2.0), the longest wait, max_delay_ms (10000), the jitter, a fraction
by which each wait is made longer or shorter at random (0.2), so that many
clients do not try again at the same moment, and retry_if, which says
whether an error is worth another try:
RetryOptions options = RetryOptions()
options.retry_if = func(Error e) => e.kind != "not_found"
str page = await retry(func() => download(url), options)?all_of, any_of, and_then, and recover have these names because
all, any, then, and catch are taken by the functions for arrays and
by keywords. The other modules have tasks of their own:
finished and capture_task for processes,
and next_change for files.