lloyAlloy on GitHub
Future
Docs

Future

Updated
Future<T>
type Profile = { id: number }

async function load(id: number): Profile
    task.wait(1)
    return { id = id }
end
async do
    local profiles = await Future.all([load(1), load(2)])
    local first = await Future.race([load(1), Future.delay(5)])
    print(profiles, first)
end

A memoized task, from the std: it runs once, settles once, and every await after that reads the same value. An async function returns one and its body runs on task.spawn under xpcall; await yields until it settles, then returns the value or rethrows; try await returns the Err from the enclosing function instead of rethrowing. Both need an async context: see alloy doc await.

An error in a body settles its Future as rejected, and the Future keeps it: nothing prints it. An async do block or an async function call that no code awaits drops its error that way. await the Future, or pass a second callback to and_then, to see the error.

race and any read the value type off each Future in the list, so a mixed list gives the union: Future<number> beside Future<nil> yields Future<number?>. An async function without a return type and without a return value is Future<nil>. A header may name the answer itself, async function f(): Future<T>, or what it settles with, async function f(): T; the two mean one thing.

Statics

Future.resolve

Future.resolve<T>(value: T): Future<T>

A Future that has already settled with value. Every await on it returns at once.

local ready = Future.resolve(42)
async do print(await ready) end

Future.reject

Future.reject(error: any): Future<any>

A Future that has already failed with error. An await on it rethrows, and try await returns that Err from the function. The payload reads any: a rejected Future never settles with one, so it stands where a Future<T> is asked for.

local bad = Future.reject("no disk")
bad:andThen(nil, function(e) print(e) end)

Future.delay

Future.delay(seconds: number): Future<()>

A Future that settles after the wait, with no value. It is the async form of task.wait.

async do
    await Future.delay(0.5)
    print("later")
end

Future.all

Future.all<T>(futures: Future<T>[]): Future<T[]>

One Future of every value, in the order of the list. The first failure fails the whole thing, and the rest keep running. Every Future in the list carries one type here; race and any are the ones that take a mixed list.

async do
    local both = await Future.all([Future.resolve(1), Future.resolve(2)])
    print(both:len())
end

Future.race

Future.race<T>(futures: Future<T>[]): Future<T>

The first Future to settle, whether it succeeds or fails. Use it for a timeout beside real work. The futures in the list need not carry one type: the result is the union of their value types, so a value beside a Future.delay is optional.

async do
    local first = await Future.race([Future.resolve(1), Future.resolve(2)])
    print(first)
end

Future.any

Future.any<T>(futures: Future<T>[]): Future<T>

The first Future to succeed. It fails only when every one of them fails. As with race, a list whose futures carry different types gives the union of them.

async do
    local ok = await Future.any([Future.reject("a"), Future.resolve(2)])
    print(ok)
end

Future.all_settled

Future.all_settled(futures: Future<any>[]): Future<any[]>

One Result per Future, in order. No failure fails it. The checker reads each Result as any, since a list of Results under a Future reaches its limit. Annotate the local to type them: local f: Future<Result<number, any>[]> = Future.all_settled(xs). The error side is any, since a rejection carries whatever it was rejected with.

async do
    local rs = await Future.all_settled([Future.resolve(1), Future.reject("no")])
    print(rs:len())
end

Methods

Future:andThen

Future:andThen(on_resolve: ((T) -> ...any)?, on_reject: ((any) -> ...any)?): Future<T>

Runs a callback when the Future settles and returns the same Future, so calls chain. What the callback returns is dropped: the Future keeps its own value.

local f = Future.resolve(1):andThen(function(n) print(n) end)
async do print(await f) end

Future:and_then

Future:and_then(on_resolve: ((T) -> ...any)?, on_reject: ((any) -> ...any)?): Future<T>

The same function under a snake_case name. andThen is the one camelCase name the std carries, so a file that keeps to one spelling writes this.

local f = Future.resolve(1):and_then(function(n) print(n) end)
async do print(await f) end

Future:cancel

Future:cancel()

Closes the task behind the Future. An await on a cancelled Future raises.

local slow = Future.delay(10)
slow:cancel()

Future:is_settled

Future:is_settled(): boolean

Whether the Future has a value or a failure. A cancelled Future is not settled.

local ready = Future.resolve(1)
print(ready:is_settled())