A Minimal, Typed Future Implementation inspired by the concept of Futures from the Rust Ecosystem.
Futures are a Data-Driven approach to asynchronous calls, what this means is that Futures represent a value that does not exist quite yet, similar to Promises.
Unlike Promises, Futures take on a Data-Driven approach as opposed to a Event-Driven approach.
Futures have no events for you to react to, there is no andThen
or any other event-like methods
or functions for Futures.
In order to use Futures, you must do something called polling, you can call the isReady
method to see if the
future has a result ready, and then you can call the output
method to receive a result.
The future can either be ok or an error, you can use the ok
and error
methods respectively to check.
To get the value T
or Error
you can call the unwrap
method on the result.
Don't. Use Promises. You shouldn't need to use this and shouldn't unless it fits a certain use case. Promises have Chaining, Joining, Cancellation, and many more features that Futures don't have.
Futures are simply a lightweight alternative to Promises that uses long polling. Any gains you may get from using Futures will be so insignificant you'd only be hurting yourself by using them in most cases.
See Roblox Lua Promise and Why Use Promises? by Evaera
When writing code that ran every frame I found myself needing to represent yielding asynchronous calls in a way where I could store the future value and use it in a future frame. Promises felt like a good first step, but it just felt like they did not fit the Data-Driven architecture I was going for, with Promises being Event-Driven.
So here comes Luau Futures, a Data-Driven Approach to handling asynchronous code, built for a library such as Matter, which had no built-in method for handling Asynchronous Calls.
local Future = require(path.to.module)
-- Create a future
local myFuture = Future.new(function(...)
-- Something that yields
end, ...)
-- Poll the Future to see if it is ready.
if future:isReady() then
local result = myFuture:output()
if result:ok() then
local returnedValues = result:unwrap()
-- Do something
elseif result:error() then
warn(result:unwrap())
end
elseif myFuture:isPending() then
-- Poll the Future to see if it is still pending.
warn("Future is still pending!")
end
In a Matter System:
-- Basic concept of Futures in a Matter System
local function exampleSystem(world)
-- Create Futures
for id in world:query():without(FutureComponent) do
world:insert(id, FutureComponent {
future = Future.new(function()
-- Something that yields
end)
})
end
-- Poll Futures
for id, future in world:query(FutureComponent) do
local future = future.future
if future:isReady() then
local result = myFuture:output()
if result:ok() then
local returnedValues = result:unwrap()
-- Do something
elseif result:error() then
warn(result:unwrap())
end
world:remove(id, FutureComponent)
end
end
end
[dependencies]
Future = "yetanotherclown/future@1.1.0"
Note: Wally does not export types automatically and will display a type-error in one of the Dependencies. To fix this, see https://github.com/JohnnyMorganz/wally-package-types.
To build yourself, use:
rojo build -o "Future.rbxm"
For more help, check out the Rojo documentation.