yetanotherclown/luau_futures
Rust-like futures for Luau
Luau Futures
Futures represent a read-only asynchronous value, one that may not have finished computation like.
This design is inspired by the Futures crate in Rust.
Info
After almost two years of being the oldest Futures implementation on Wally, Luau Futures v2.0.0 has released, with several key changes.
Importantly, the Wally scope has been changed to yetanotherclown/luau-futures.
If you are still using the v1.x.x Future library make sure to update your wally.toml to upgrade.
You can find out more here.
Basic Use
Creating a future is very simple:
local Futures = require("@packages/Futures")
local Future = Futures.Future
local myFuture = Future.new(function()
yield()
return 1, 2, 3
end)
When you create a future, it wont begin execution until it is either polled or awaited.
Polling will advance the future to it's next resumption point every time that it is called, returning a Poll to let you check the status of the future.
If the Poll is ready, you can also unwrap it to get the Result
local poll = myFuture:poll()
if poll:isReady() then
local result = poll:unwrap()
-- Handle result
end
Awaiting a future will yield the current thread until the future finishes execution. As such, it is recommended that you only use the await method within other futures, preferring to use poll instead.
local result = myFuture:await()
-- Handle result
To read the result, you can use Result
or Result to check what type the Result is.You can then use Result
or Result to get the value of the result.if result:isOk() then
print(result:unwrapOk()) -- 1, 2, 3
elseif result:isErr() then
warn(result:unwrapErr()) -- An error occurred
end
There are also several other methods for chaining, combining, and mapping futures, as well as other utilities for working with futures.
It is suggested to read the API Documentation for more information about these methods.
Why Luau Futures
Laziness
Like in Rust, Luau Future is lazy. Unlike Promises which are eager.
Futures will not begin execution until polled or awaited, where as in Promises, execution is begun immediately or scheduled to be done as soon as it can.
Polling will execute until the next suspension point, until execution is finished. By awaiting a Future, it will yield the current thread until execution has completed.
Strictly Typed
Strict Typing is a feature, with API designed to work with the Luau type solver.
There are currently some restrictions, see below for more information.
Functional
The API is designed to be functional, taking inspiration from the Rust futures crate.
Why you Shouldn't Use Luau Futures
Futures are Lazy
Sometimes, you might not want the Laziness of Futures, and instead want execution to begin when it can. Promises begin execution as soon as they're made, allowing the result to be completed much sooner than with a Future. Futures are lazy by design, you might find that you want this laziness for a certain purpose and that is fine, but sometimes you might not.
Promises Just Work
roblox-lua-promise works, and has worked for some time now. Do you need a battle tested strategy for asynchronous programming? Use roblox-lua-promise! If you're already using Promises, keep using them.
Promises are more Common
Working on a library? Introducing new developers to your team? It would be easier for them to understand Promises, as they're already widely popular in the JavaScript ecosystem as well as in Luau.
A Note on Typechecking
The following typechecking restrictions should be resolved in the Luau Solver V2, in which recursive type restrictions should be loosened.
Exported Types
The Futures library exports two types because of these restrictions. FutureLike should be used when your being given a future, such as in a function with a future as a parameter. The Future type should be used when returning a future, such as in a function return.
function Class:method<E, U...>(future: Futures.FutureLike<E, U...>): Futures.Future<E, U...>
return future:andThen(function(...)
-- ...
end) :: any
end
Note
To avoid recursive type restrictions, there are internally multiple types like FutureFirst, FutureNext, FutureLast and FutureExhausted.
The Futures.Future type is just FutureFirst, so when you use that type it will expect a FutureFirst which is the first type you get when creating a future with Future.new().
If you are chaining a future in a function that returns one, you can annotate the return type to be Futures.Future and then typecast the returned future with :: any like in the example.
Recursive Types
Some methods, such as andThen, mapOk, mapErr, etc. will return a recursive type with different parameters.
Currently, there are restrictions in place in the Luau type solver to prevent this. The Futures library has a workaround
to allow you to chain up to 3 of these methods. When you hit the limit, you will be returned a generic
Future that is typed as Future<any, ...any>.
Join Methods
The Join methods currently will always return a generic Future. Currently, it is impossible to type these methods.
UnwrapOrElse
should return the typeFuture<never, U...>. However, due to recursive type restrictions, it will return Future<E, U...>.