Promise.lua
🦔 🦔 🦔
A JavaScript-style Promise library for Lua.
You can play with it above by messing with the Promise
object and also setTimeout
and setInterval
which work exactly as you'd expect them to work in JavaScript.
The API
I tried to cover most of the API I saw on MDN:
- Constructor
Promise.new(fn)
– returns a new Promise.fn
is a function which takes in two parameters,resolve
andreject
, both of which are functions that will either resolve or reject the promise with the given value. If the value given toresolve
is a promise or a table that has anext
method, it will use the result of that method instead.
- Instance methods
:next(onFulfilled, onRejected)
– registers callbacks to run when the promise is settled (either fulfilled or rejected). This is callednext
instead ofthen
asthen
is a reserved keyword in Lua.:catch(onRejected)
– registers a callback to run when the promise is rejected.:finally(onFinally)
– registers a callback that will run when the promise is settled. This promise will settle with whatever the original promise's state is.
- Static methods
Promise.all(promises)
– takes a list of promises and returns a promise that is fulfilled when all input promises are fulfilled or is rejected when any of the promises are rejected.Promise.allSettled(promises)
– takes a list of promises and returns a promise that is fulfilled when all input promises are settled with a list of tables in the shape of{ status = "fulfilled", value = <...> }
or{ status = "rejected", reason = <...> }
.Promise.any(promises)
– takes a list of promises and returns a promise that is fulfilled when any of the input promises are fulfilled or is rejected when all of the input promises are rejected.Promise.race(promises)
– takes a list of promises and returns a promise that is settled with the state of the first promise in the list to settle.Promise.reject(reason)
– returns a promise that's rejected with the given reason.Promise.resolve(value)
– returns a promise that's resolved with the given value.Promise.try(fn)
– takes a callback of any kind and wraps its result in a promise.Promise.withResolvers()
– returns a promise, itsresolve
function, and itsreject
function. I also added a few extra helpers for my own sake:
- Async-await
Promise.async(fn)
– returns a function that returns a promise that will be resolved or rejected based on the result of executingfn
in a coroutine.:await()
– if executed from a coroutine, will yield until the promise is settled and return the resolved value orerror
with the rejection reason.
- Various
:ok()
– converts rejections of the promise intonil
, think Rust's Result.ok().:print()
– prints the settled result of the promise without modifying it.
I've kept this async runtime-agnostic. You can override the Promise.schedule(fn)
function with your own implementation that will schedule functions for later asynchronous execution. An example implementation for Hammerspoon is provided in the repository.
Making of
I've been suffering from callback hell when automating things with Hammerspoon, so I ended up using zserge/lua-promises (deferred
) for a while, but then decided to write my own because reinventing the wheel is a good way to learn how the wheel works.
It was pretty fun to make. The TC39 specification for JavaScript is insanely detailed, so I always could count on it to help me resolve any ambiguities, though I didn't implement it word for word.
I actually get quite a lot of use out of those async-await functions in Hammerspoon, as they let me completely flatten functions that deal with UI automation, which can require waiting for animations to finish and so on.