Async with Futures
The future
utility can be used to represent asynchronous computations.
A future represents a value that does not exist yet, but will exist at some point in the future.
They're useful to represent asynchronous computation such as network requests.
Creating futures
A future can be in two states: Pending
or Finished
. The Pending
state represents a computation that is still running, and the Finished
state represents a computation that has finished.
When a future finishes, that is also known as "resolving".
To create a future that is already finished, we can use Future.new
:
local fut = Future.new(5)
To create a future that is pending, and will never resolve, you can use Future.never
:
local fut = Future.never()
Though, we want our futures to do something, to do this, we can use Future.spawn
which will spawn a new thread and run the given function:
local function httpGet(url)
-- some code to make a network request
end
local fut = Future.spawn(httpGet, "https://google.com/")
This future will resolve with the result of the httpGet
function.
You may also pass an anonymous function to Future.spawn
:
local fut = Future.spawn(function()
return 5
end)
We can return futures from functions to represent asynchronous computations:
function httpGet(url: string): Future.Future<string>
return Future.spawn(function()
-- some code to make a network request
end)
end
MEMORY USAGE
The above code will create a closure for each call to httpGet
, which may cause high memory usage. To avoid this, you can pass any values the function needs as arguments:
function httpGet(url: string): Future.Future<string>
return Future.spawn(function(url)
-- some code to make a network request
end, url)
end
-- you may also define the function separately if you prefer:
local function httpGet(url: string): Future.Future<string>
local function inner(url: string): string
-- some code to make a network request
end
return Future.spawn(inner, url)
end
This will allow Luau to cache the function and reuse it across calls, reducing memory usage.
Accessing the inner value
We now have futures that will resolve at some point, but how do we access the inner value?
One way is the now
method, which will return an option containing the current value, or None
if the future is still pending:
local fut = Future.new(5)
local value = fut:now():unwrap()
print(value) -- 5
However, usually we want to wait for the future to resolve, and then do something with the value. We can use the await
method for this:
local fut = Future.spawn(httpGet, "https://google.com/")
local value = fut:await()
print(value) -- 5
This will yield the current thread until the future resolves, and then return the value.
You may also use the after
method to execute a callback when the future resolves:
local fut = Future.spawn(httpGet, "https://google.com/")
fut:after(function(value)
print(value) -- 5
end)
The callback is executed in a separate thread when the future resolves.
Composing futures
The future utility provides a few methods to compose futures.
The all
function can be used to wait for every future in an array to resolve:
local fut1 = Future.new(5)
local fut2 = Future.new(10)
local fut = Future.all({ fut1, fut2 })
local values = fut:await()
print(values[1]) -- 5
print(values[2]) -- 10
This will wait for all given futures to resolve, and resolves to an array of the resolved values.
The race
utility can be used to wait for the first future in an array to resolve:
local fut1 = Future.new(5)
local fut2 = Future.new(10)
local fut = Future.race({ fut1, fut2 })
local value = fut:await()
print(value) -- 5
This will wait for the first future to resolve, and resolves to the value of the first resolved future.
Error handling
Futures do not support throwing errors, to handle errors, you can use futures in combination with results.
For example, the following code will call a yielding function that may fail and wraps it in a future and result:
local fut = Future.spawn(Result.try, httpGet, "https://google.com/")
You can then await the future and handle the result:
local res = fut:await()
res:match({
Ok = function(value)
print("Got a response:", value)
end,
Err = function(err)
warn("Failed to do the request:", err)
end
})
Learn more
There are more methods available on the Future
type, this was just a small tour of what you can do with futures. If you want to learn more, see the reference for more information.