mirror of
https://github.com/AmberGraceRblx/luau-promise.git
synced 2025-04-24 15:50:01 +00:00
Typesafe Promise implementation for Roblox Luau
- Always iterate over promises via `ipairs` - Avoid pushing the call arguments stack to a table in Promise.prototype.expect and Promise.prototype.await - Use a doubly-linked list implementation of a queue The old queue/dequeue implementation used an array which: - has items removed from the front (`table.remove(queue, 1)` O(n) each time) - this is especially bad in the main loop which could run multiple times in-a-row on a large array - new: O(1) - uses table.insert() followed by table.sort() to add a new node (O(n log n)) - new: O(n) - has to lookup the index of the node being dequeued (O(n)) - new: O(1) |
||
---|---|---|
.vuepress | ||
lib | ||
modules | ||
.editorconfig | ||
.gitignore | ||
.gitmodules | ||
.luacheckrc | ||
.luacov | ||
CHANGELOG.md | ||
default.project.json | ||
LICENSE | ||
package-lock.json | ||
package.json | ||
README.md | ||
rotriever.toml | ||
spec.lua |
Why you should use Promises
The way Roblox models asynchronous operations by default is by yielding (stopping) the thread and then resuming it when the future value is available. This model is not ideal because:
- Functions you call can yield without warning, or only yield sometimes, leading to unpredictable and surprising results. Accidentally yielding the thread is the source of a large class of bugs and race conditions that Roblox developers run into.
- It is difficult to deal with running multiple asynchronous operations concurrently and then retrieve all of their values at the end without extraneous machinery.
- When an asynchronous operation fails or an error is encountered, Lua functions usually either raise an error or return a success value followed by the actual value. Both of these methods lead to repeating the same tired patterns many times over for checking if the operation was successful.
- Yielding lacks easy access to introspection and the ability to cancel an operation if the value is no longer needed.
This Promise implementation attempts to satisfy these traits:
- An object that represents a unit of asynchronous work
- Composability
- Predictable timing
Example
Promise.async
returns synchronously.
local HttpService = game:GetService("HttpService")
-- A light wrapper around HttpService
-- Ideally, you do this once per project per async method that you use.
local function httpGet(url)
return Promise.async(function(resolve, reject)
local ok, result = pcall(HttpService.GetAsync, HttpService, url)
if ok then
resolve(result)
else
reject(result)
end
end)
end
-- Usage
httpGet("https://google.com")
:andThen(function(body)
print("Here's the Google homepage:", body)
end)
:catch(function(err)
warn("We failed to get the Google homepage!", err)
end)