Initial forked version

This commit is contained in:
Amber's Careware 2022-10-24 20:14:27 -06:00
parent 2c6f433903
commit 558b61fe3d
12 changed files with 259 additions and 92 deletions

1
.github/FUNDING.yml vendored
View file

@ -1 +0,0 @@
patreon: erynlynn

3
.gitignore vendored
View file

@ -2,4 +2,5 @@
node_modules/ node_modules/
.vscode .vscode
build build
roblox.toml roblox.toml
sourcemap.json

View file

@ -1,5 +1,14 @@
# Changelog # Changelog
## FORKED VERSION [4.0.0]
- Added `any` type annotations to many variables in the internal implementation in order to prevent crashes while using this library
- Added `--!nonstrict` compiler directive at the top of the script to expose types
- Added library-user-facing typings for `Promise`, `Error`, `Status` and the library itself. This fork uses the `(object :: any) :: PublicType` idiom to simplify the types to the library, allowing for better autocompletion and type annotations in a strict-mode Luau codebase
No other changes have been made to the library's runtime behavior.
Please notify me through the issues section if any issues are encountered related to type safety or crashes. Otherwise, please send any issues related to runtime behavior to the [original repository](https://github.com/evaera/roblox-lua-promise).
## [4.0.0] ## [4.0.0]
### Changed ### Changed
- `Promise:finally` no longer observes a rejection from a Promise. Calling `Promise:finally` is mostly transparent now. - `Promise:finally` no longer observes a rejection from a Promise. Calling `Promise:finally` is mostly transparent now.

View file

@ -1,5 +1,6 @@
MIT License MIT License
Copyright (c) 2022 Amber Grace
Copyright (c) 2019 Eryn L. K. Copyright (c) 2019 Eryn L. K.
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy

View file

@ -1,11 +1,10 @@
<div align="center"> <div align="center">
<h1>Roblox Lua Promise</h1> <h1>Roblox Luau Promise</h1>
<p>An implementation of <code>Promise</code> similar to Promise/A+.</p> <p>An implementation of <code>Promise</code> similar to Promise/A+.</p>
<a href="https://eryn.io/roblox-lua-promise/"><strong>View docs</strong></a> <a href="https://eryn.io/roblox-lua-promise/"><strong>View docs</strong></a>
</div> </div>
<!--moonwave-hide-before-this-line--> <!--moonwave-hide-before-this-line-->
## Why you should use Promises ## 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: 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:
@ -20,3 +19,9 @@ This Promise implementation attempts to satisfy these traits:
* An object that represents a unit of asynchronous work * An object that represents a unit of asynchronous work
* Composability * Composability
* Predictable timing * Predictable timing
## FORKED VERSION
This is a forked version of [Evaera's Promise library](https://github.com/evaera/roblox-lua-promise) with type annotations added to allow this library to be used in a strict-mode [Luau](https://luau-lang.org) project. Type annotations are imperfect due to limitations with the Luau language, but should at least cover intellisense and basic static analysis.
Please notify me through the issues section if any issues are encountered related to type safety or crashes. Otherwise, please send any issues related to runtime behavior to the [original repository](https://github.com/evaera/roblox-lua-promise).

26
lib/LICENSE.luau Normal file
View file

@ -0,0 +1,26 @@
return [[
MIT License
Copyright (c) 2022 Amber Grace
Copyright (c) 2019 Eryn L. K.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]

View file

@ -1,13 +1,144 @@
--!nonstrict
--[[ --[[
An implementation of Promises similar to Promise/A+. An implementation of Promises similar to Promise/A+
Original library by Eryn L. K.
Edit by Amber Grace (@DataBrain) to include custom type annotations & extensions
Forked from github repository: evaera/roblox-lua-promise
Forked from release V4.0.0 (Mar 2nd 2022).
I do not intend to maintain
this forked version of the library unless a major bug or exploit is
found with the original library that needs to be patched here.
Licensed under MIT License (see nested module for full license)
]] ]]
export type Status = "Started" | "Resolved" | "Rejected" | "Cancelled"
export type Executor = (
resolve: (...any) -> (),
reject: (...any) -> (),
onCancel: (abortHandler: (() -> ())?) -> boolean
) -> ()
export type Promise = {
timeout: (self: Promise, seconds: number, rejectionValue: any?) -> Promise,
getStatus: (self: Promise) -> Status,
andThen: (
self: Promise,
successHandler: (...any) -> ...any,
failureHandler: ((...any) -> ...any)?
) -> Promise,
catch: (
self: Promise,
failureHandler: (...any) -> ...any
) -> Promise,
tap: (
self: Promise,
tapHandler: (...any) -> ...any
) -> Promise,
andThenCall: <T...>(
self: Promise,
callback: (T...) -> any,
T...
) -> Promise,
andThenReturn: (
self: Promise,
...any
) -> Promise,
cancel: (self: Promise) -> (),
finally: (
self: Promise,
finallyHandler: (status: Status) -> ...any
) -> Promise,
finallyCall: <T...>(
self: Promise,
callback: (T...) -> any,
T...
) -> Promise,
finallyReturn: (
self: Promise,
...any
) -> Promise,
awaitStatus: (
self: Promise
) -> (Status, ...any),
await: (self: Promise) -> (boolean, ...any),
expect: (self: Promise) -> ...any,
now: (self: Promise, rejectionValue: any?) -> Promise,
}
export type ErrorKind = "ExecutionError" | "AlreadyCancelled" | "NotResolvedInTime" | "TimedOut"
export type ErrorOptions = {
error: string?,
trace: string?,
context: string?,
kind: ErrorKind
}
export type Error = {
kind: ErrorKind,
trace: string?,
context: string?,
parent: Error?,
error: string,
createdTick: number,
createdTrace: string,
extend: (self: Error, options: ErrorOptions) -> Error,
getErrorChain: (self: Error) -> {Error},
}
type PromiseLib = {
new: (executor: Executor) -> Promise,
defer: (executor: Executor) -> Promise,
resolve: (...any) -> Promise,
reject: (...any) -> Promise,
try: <T...>(
callback: (T...) -> ...any,
T...
) -> Promise,
all: (promises: {Promise}) -> Promise,
fold: (
list: {any},
reducer: (accumulator: any, value: any, index: number) -> any,
initialValue: any
) -> (),
some: (promises: {Promise}, count: number) -> Promise,
any: (promises: {Promise}) -> Promise,
allSettled: (promises: {Promise}) -> Promise,
race: (promises: {Promise}) -> Promise,
each: (
list: {any},
predicate: (value: any, index: number) -> any
) -> Promise,
is: (object: any) -> boolean,
promisify: <T...>(
callback: (T...) -> ...any
) -> ((T...) -> Promise),
delay: (seconds: number) -> Promise,
retry: <P...>(
callback: (P...) -> Promise,
times: number,
P...
) -> Promise,
retryWithDelay: <P...>(
callback: (P...) -> Promise,
times: number,
seconds: number,
P...
) -> Promise,
fromEvent: (
event: RBXScriptSignal | {Connect: any},
predicate: ((...any) -> boolean)?
) -> Promise,
onUnhandledRejection: (
callback: (Promise, ...any) -> ()
) -> (() -> ())
}
local ERROR_NON_PROMISE_IN_LIST = "Non-promise value passed into %s at index %s" local ERROR_NON_PROMISE_IN_LIST = "Non-promise value passed into %s at index %s"
local ERROR_NON_LIST = "Please pass a list of promises to %s" local ERROR_NON_LIST = "Please pass a list of promises to %s"
local ERROR_NON_FUNCTION = "Please pass a handler function to %s!" local ERROR_NON_FUNCTION = "Please pass a handler function to %s!"
local MODE_KEY_METATABLE = { __mode = "k" } local MODE_KEY_METATABLE: any = { __mode = "k" }
local function isCallable(value) local function isCallable(value: any)
if type(value) == "function" then if type(value) == "function" then
return true return true
end end
@ -25,7 +156,7 @@ end
--[[ --[[
Creates an enum dictionary with some metamethods to prevent common mistakes. Creates an enum dictionary with some metamethods to prevent common mistakes.
]] ]]
local function makeEnum(enumName, members) local function makeEnum(enumName: any, members: any)
local enum = {} local enum = {}
for _, memberName in ipairs(members) do for _, memberName in ipairs(members) do
@ -49,7 +180,7 @@ end
@class Error @class Error
]=] ]=]
local Error local Error: any
do do
Error = { Error = {
Kind = makeEnum("Promise.Error.Kind", { Kind = makeEnum("Promise.Error.Kind", {
@ -61,7 +192,7 @@ do
} }
Error.__index = Error Error.__index = Error
function Error.new(options, parent) function Error.new(options: any, parent: any)
options = options or {} options = options or {}
return setmetatable({ return setmetatable({
error = tostring(options.error) or "[This error has no error text.]", error = tostring(options.error) or "[This error has no error text.]",
@ -74,7 +205,7 @@ do
}, Error) }, Error)
end end
function Error.is(anything) function Error.is(anything: any)
if type(anything) == "table" then if type(anything) == "table" then
local metatable = getmetatable(anything) local metatable = getmetatable(anything)
@ -86,13 +217,13 @@ do
return false return false
end end
function Error.isKind(anything, kind) function Error.isKind(anything: any, kind: any)
assert(kind ~= nil, "Argument #2 to Promise.Error.isKind must not be nil") assert(kind ~= nil, "Argument #2 to Promise.Error.isKind must not be nil")
return Error.is(anything) and anything.kind == kind return Error.is(anything) and anything.kind == kind
end end
function Error:extend(options) function Error:extend(options: any)
options = options or {} options = options or {}
options.kind = options.kind or self.kind options.kind = options.kind or self.kind
@ -134,21 +265,21 @@ end
Used to cajole varargs without dropping sparse values. Used to cajole varargs without dropping sparse values.
]] ]]
local function pack(...) local function pack(...: any)
return select("#", ...), { ... } return select("#", ...), { ... }
end end
--[[ --[[
Returns first value (success), and packs all following values. Returns first value (success), and packs all following values.
]] ]]
local function packResult(success, ...) local function packResult(success: any, ...: any)
return success, select("#", ...), { ... } return success, select("#", ...), { ... }
end end
local function makeErrorHandler(traceback) local function makeErrorHandler(traceback: any)
assert(traceback ~= nil, "traceback is nil") assert(traceback ~= nil, "traceback is nil")
return function(err) return function(err: any)
-- If the error object is already a table, forward it directly. -- If the error object is already a table, forward it directly.
-- Should we extend the error here and add our own trace? -- Should we extend the error here and add our own trace?
@ -168,7 +299,7 @@ end
--[[ --[[
Calls a Promise executor with error handling. Calls a Promise executor with error handling.
]] ]]
local function runExecutor(traceback, callback, ...) local function runExecutor(traceback: any, callback: any, ...: any)
return packResult(xpcall(callback, makeErrorHandler(traceback), ...)) return packResult(xpcall(callback, makeErrorHandler(traceback), ...))
end end
@ -176,8 +307,8 @@ end
Creates a function that invokes a callback with correct error handling and Creates a function that invokes a callback with correct error handling and
resolution mechanisms. resolution mechanisms.
]] ]]
local function createAdvancer(traceback, callback, resolve, reject) local function createAdvancer(traceback: any, callback: any, resolve: any, reject: any)
return function(...) return function(...: any)
local ok, resultLength, result = runExecutor(traceback, callback, ...) local ok, resultLength, result = runExecutor(traceback, callback, ...)
if ok then if ok then
@ -188,7 +319,7 @@ local function createAdvancer(traceback, callback, resolve, reject)
end end
end end
local function isEmpty(t) local function isEmpty(t: any)
return next(t) == nil return next(t) == nil
end end
@ -217,22 +348,23 @@ end
@class Promise @class Promise
@__index prototype @__index prototype
]=] ]=]
local Promise = { local Promise: any = {
Error = Error, Error = Error,
Status = makeEnum("Promise.Status", { "Started", "Resolved", "Rejected", "Cancelled" }), Status = makeEnum("Promise.Status", { "Started", "Resolved", "Rejected", "Cancelled" }),
_getTime = os.clock, _getTime = os.clock,
_timeEvent = game:GetService("RunService").Heartbeat, _timeEvent = game:GetService("RunService").Heartbeat,
_unhandledRejectionCallbacks = {}, _unhandledRejectionCallbacks = {},
TEST = nil :: boolean?,
} }
Promise.prototype = {} Promise.prototype = {}
Promise.__index = Promise.prototype Promise.__index = Promise.prototype
function Promise._new(traceback, callback, parent) function Promise._new(traceback: any, callback: any, parent: any)
if parent ~= nil and not Promise.is(parent) then if parent ~= nil and not Promise.is(parent) then
error("Argument #2 to Promise.new must be a promise or nil", 2) error("Argument #2 to Promise.new must be a promise or nil", 2)
end end
local self = { local self: any = {
-- The executor thread. -- The executor thread.
_thread = nil, _thread = nil,
@ -275,15 +407,15 @@ function Promise._new(traceback, callback, parent)
setmetatable(self, Promise) setmetatable(self, Promise)
local function resolve(...) local function resolve(...: any)
self:_resolve(...) self:_resolve(...)
end end
local function reject(...) local function reject(...: any)
self:_reject(...) self:_reject(...)
end end
local function onCancel(cancellationHook) local function onCancel(cancellationHook: any)
if cancellationHook then if cancellationHook then
if self._status == Promise.Status.Cancelled then if self._status == Promise.Status.Cancelled then
cancellationHook() cancellationHook()
@ -346,7 +478,7 @@ end
@param executor (resolve: (...: any) -> (), reject: (...: any) -> (), onCancel: (abortHandler?: () -> ()) -> boolean) -> () @param executor (resolve: (...: any) -> (), reject: (...: any) -> (), onCancel: (abortHandler?: () -> ()) -> boolean) -> ()
@return Promise @return Promise
]=] ]=]
function Promise.new(executor) function Promise.new(executor: any)
return Promise._new(debug.traceback(nil, 2), executor) return Promise._new(debug.traceback(nil, 2), executor)
end end
@ -372,7 +504,7 @@ end
@param executor (resolve: (...: any) -> (), reject: (...: any) -> (), onCancel: (abortHandler?: () -> ()) -> boolean) -> () @param executor (resolve: (...: any) -> (), reject: (...: any) -> (), onCancel: (abortHandler?: () -> ()) -> boolean) -> ()
@return Promise @return Promise
]=] ]=]
function Promise.defer(executor) function Promise.defer(executor: any)
local traceback = debug.traceback(nil, 2) local traceback = debug.traceback(nil, 2)
local promise local promise
promise = Promise._new(traceback, function(resolve, reject, onCancel) promise = Promise._new(traceback, function(resolve, reject, onCancel)
@ -415,7 +547,7 @@ Promise.async = Promise.defer
@param ... any @param ... any
@return Promise<...any> @return Promise<...any>
]=] ]=]
function Promise.resolve(...) function Promise.resolve(...: any)
local length, values = pack(...) local length, values = pack(...)
return Promise._new(debug.traceback(nil, 2), function(resolve) return Promise._new(debug.traceback(nil, 2), function(resolve)
resolve(unpack(values, 1, length)) resolve(unpack(values, 1, length))
@ -432,7 +564,7 @@ end
@param ... any @param ... any
@return Promise<...any> @return Promise<...any>
]=] ]=]
function Promise.reject(...) function Promise.reject(...: any)
local length, values = pack(...) local length, values = pack(...)
return Promise._new(debug.traceback(nil, 2), function(_, reject) return Promise._new(debug.traceback(nil, 2), function(_, reject)
reject(unpack(values, 1, length)) reject(unpack(values, 1, length))
@ -443,7 +575,7 @@ end
Runs a non-promise-returning function as a Promise with the Runs a non-promise-returning function as a Promise with the
given arguments. given arguments.
]] ]]
function Promise._try(traceback, callback, ...) function Promise._try(traceback: any, callback: any, ...: any)
local valuesLength, values = pack(...) local valuesLength, values = pack(...)
return Promise._new(traceback, function(resolve) return Promise._new(traceback, function(resolve)
@ -474,7 +606,7 @@ end
@param ... T... -- Additional arguments passed to `callback` @param ... T... -- Additional arguments passed to `callback`
@return Promise @return Promise
]=] ]=]
function Promise.try(callback, ...) function Promise.try(callback: any, ...: any)
return Promise._try(debug.traceback(nil, 2), callback, ...) return Promise._try(debug.traceback(nil, 2), callback, ...)
end end
@ -483,7 +615,7 @@ end
* is resolved when all input promises resolve * is resolved when all input promises resolve
* is rejected if ANY input promises reject * is rejected if ANY input promises reject
]] ]]
function Promise._all(traceback, promises, amount) function Promise._all(traceback: any, promises: any, amount: any)
if type(promises) ~= "table" then if type(promises) ~= "table" then
error(string.format(ERROR_NON_LIST, "Promise.all"), 3) error(string.format(ERROR_NON_LIST, "Promise.all"), 3)
end end
@ -588,7 +720,7 @@ end
@param promises {Promise<T>} @param promises {Promise<T>}
@return Promise<{T}> @return Promise<{T}>
]=] ]=]
function Promise.all(promises) function Promise.all(promises: any)
return Promise._all(debug.traceback(nil, 2), promises) return Promise._all(debug.traceback(nil, 2), promises)
end end
@ -617,7 +749,7 @@ end
@param reducer (accumulator: U, value: T, index: number) -> U | Promise<U> @param reducer (accumulator: U, value: T, index: number) -> U | Promise<U>
@param initialValue U @param initialValue U
]=] ]=]
function Promise.fold(list, reducer, initialValue) function Promise.fold(list: any, reducer: any, initialValue: any)
assert(type(list) == "table", "Bad argument #1 to Promise.fold: must be a table") assert(type(list) == "table", "Bad argument #1 to Promise.fold: must be a table")
assert(isCallable(reducer), "Bad argument #2 to Promise.fold: must be a function") assert(isCallable(reducer), "Bad argument #2 to Promise.fold: must be a function")
@ -650,7 +782,7 @@ end
@param count number @param count number
@return Promise<{T}> @return Promise<{T}>
]=] ]=]
function Promise.some(promises, count) function Promise.some(promises: any, count: any)
assert(type(count) == "number", "Bad argument #2 to Promise.some: must be a number") assert(type(count) == "number", "Bad argument #2 to Promise.some: must be a number")
return Promise._all(debug.traceback(nil, 2), promises, count) return Promise._all(debug.traceback(nil, 2), promises, count)
@ -674,7 +806,7 @@ end
@param promises {Promise<T>} @param promises {Promise<T>}
@return Promise<T> @return Promise<T>
]=] ]=]
function Promise.any(promises) function Promise.any(promises: {any})
return Promise._all(debug.traceback(nil, 2), promises, 1):andThen(function(values) return Promise._all(debug.traceback(nil, 2), promises, 1):andThen(function(values)
return values[1] return values[1]
end) end)
@ -696,7 +828,7 @@ end
@param promises {Promise<T>} @param promises {Promise<T>}
@return Promise<{Status}> @return Promise<{Status}>
]=] ]=]
function Promise.allSettled(promises) function Promise.allSettled(promises: any)
if type(promises) ~= "table" then if type(promises) ~= "table" then
error(string.format(ERROR_NON_LIST, "Promise.allSettled"), 2) error(string.format(ERROR_NON_LIST, "Promise.allSettled"), 2)
end end
@ -774,7 +906,7 @@ end
@param promises {Promise<T>} @param promises {Promise<T>}
@return Promise<T> @return Promise<T>
]=] ]=]
function Promise.race(promises) function Promise.race(promises: any)
assert(type(promises) == "table", string.format(ERROR_NON_LIST, "Promise.race")) assert(type(promises) == "table", string.format(ERROR_NON_LIST, "Promise.race"))
for i, promise in pairs(promises) do for i, promise in pairs(promises) do
@ -869,7 +1001,7 @@ end
@param predicate (value: T, index: number) -> U | Promise<U> @param predicate (value: T, index: number) -> U | Promise<U>
@return Promise<{U}> @return Promise<{U}>
]=] ]=]
function Promise.each(list, predicate) function Promise.each(list: any, predicate: any)
assert(type(list) == "table", string.format(ERROR_NON_LIST, "Promise.each")) assert(type(list) == "table", string.format(ERROR_NON_LIST, "Promise.each"))
assert(isCallable(predicate), string.format(ERROR_NON_FUNCTION, "Promise.each")) assert(isCallable(predicate), string.format(ERROR_NON_FUNCTION, "Promise.each"))
@ -959,6 +1091,7 @@ function Promise.each(list, predicate)
end end
resolve(results) resolve(results)
return
end) end)
end end
@ -968,7 +1101,7 @@ end
@param object any @param object any
@return boolean -- `true` if the given `object` is a Promise. @return boolean -- `true` if the given `object` is a Promise.
]=] ]=]
function Promise.is(object) function Promise.is(object: any)
if type(object) ~= "table" then if type(object) ~= "table" then
return false return false
end end
@ -1017,8 +1150,8 @@ end
@param callback (...: any) -> ...any @param callback (...: any) -> ...any
@return (...: any) -> Promise @return (...: any) -> Promise
]=] ]=]
function Promise.promisify(callback) function Promise.promisify(callback: any)
return function(...) return function(...: any)
return Promise._try(debug.traceback(nil, 2), callback, ...) return Promise._try(debug.traceback(nil, 2), callback, ...)
end end
end end
@ -1048,7 +1181,7 @@ do
local first local first
local connection local connection
function Promise.delay(seconds) function Promise.delay(seconds: any)
assert(type(seconds) == "number", "Bad argument #1 to Promise.delay, must be a number.") assert(type(seconds) == "number", "Bad argument #1 to Promise.delay, must be a number.")
-- If seconds is -INF, INF, NaN, or less than 1 / 60, assume seconds is 1 / 60. -- If seconds is -INF, INF, NaN, or less than 1 / 60, assume seconds is 1 / 60.
-- This mirrors the behavior of wait() -- This mirrors the behavior of wait()
@ -1056,7 +1189,7 @@ do
seconds = 1 / 60 seconds = 1 / 60
end end
return Promise._new(debug.traceback(nil, 2), function(resolve, _, onCancel) return Promise._new(debug.traceback(nil, 2), function(resolve: any, _: any, onCancel: any)
local startTime = Promise._getTime() local startTime = Promise._getTime()
local endTime = startTime + seconds local endTime = startTime + seconds
@ -1177,7 +1310,7 @@ end
@param rejectionValue? any -- The value to reject with if the timeout is reached @param rejectionValue? any -- The value to reject with if the timeout is reached
@return Promise @return Promise
]=] ]=]
function Promise.prototype:timeout(seconds, rejectionValue) function Promise.prototype:timeout(seconds: any, rejectionValue: any)
local traceback = debug.traceback(nil, 2) local traceback = debug.traceback(nil, 2)
return Promise.race({ return Promise.race({
@ -1210,7 +1343,7 @@ end
The given callbacks are invoked depending on that result. The given callbacks are invoked depending on that result.
]] ]]
function Promise.prototype:_andThen(traceback, successHandler, failureHandler) function Promise.prototype:_andThen(traceback: any, successHandler: any, failureHandler: any)
self._unhandledRejection = false self._unhandledRejection = false
-- If we are already cancelled, we return a cancelled Promise -- If we are already cancelled, we return a cancelled Promise
@ -1222,7 +1355,7 @@ function Promise.prototype:_andThen(traceback, successHandler, failureHandler)
end end
-- Create a new promise to follow this part of the chain -- Create a new promise to follow this part of the chain
return Promise._new(traceback, function(resolve, reject, onCancel) return Promise._new(traceback, function(resolve: any, reject: any, onCancel: any)
-- Our default callbacks just pass values onto the next promise. -- Our default callbacks just pass values onto the next promise.
-- This lets success and failure cascade correctly! -- This lets success and failure cascade correctly!
@ -1280,7 +1413,7 @@ end
@param failureHandler? (...: any) -> ...any @param failureHandler? (...: any) -> ...any
@return Promise<...any> @return Promise<...any>
]=] ]=]
function Promise.prototype:andThen(successHandler, failureHandler) function Promise.prototype:andThen(successHandler: any, failureHandler: any)
assert(successHandler == nil or isCallable(successHandler), string.format(ERROR_NON_FUNCTION, "Promise:andThen")) assert(successHandler == nil or isCallable(successHandler), string.format(ERROR_NON_FUNCTION, "Promise:andThen"))
assert(failureHandler == nil or isCallable(failureHandler), string.format(ERROR_NON_FUNCTION, "Promise:andThen")) assert(failureHandler == nil or isCallable(failureHandler), string.format(ERROR_NON_FUNCTION, "Promise:andThen"))
@ -1307,7 +1440,7 @@ end
@param failureHandler (...: any) -> ...any @param failureHandler (...: any) -> ...any
@return Promise<...any> @return Promise<...any>
]=] ]=]
function Promise.prototype:catch(failureHandler) function Promise.prototype:catch(failureHandler: any)
assert(failureHandler == nil or isCallable(failureHandler), string.format(ERROR_NON_FUNCTION, "Promise:catch")) assert(failureHandler == nil or isCallable(failureHandler), string.format(ERROR_NON_FUNCTION, "Promise:catch"))
return self:_andThen(debug.traceback(nil, 2), nil, failureHandler) return self:_andThen(debug.traceback(nil, 2), nil, failureHandler)
end end
@ -1328,9 +1461,9 @@ end
@param tapHandler (...: any) -> ...any @param tapHandler (...: any) -> ...any
@return Promise<...any> @return Promise<...any>
]=] ]=]
function Promise.prototype:tap(tapHandler) function Promise.prototype:tap(tapHandler: any)
assert(isCallable(tapHandler), string.format(ERROR_NON_FUNCTION, "Promise:tap")) assert(isCallable(tapHandler), string.format(ERROR_NON_FUNCTION, "Promise:tap"))
return self:_andThen(debug.traceback(nil, 2), function(...) return self:_andThen(debug.traceback(nil, 2), function(...: any)
local callbackReturn = tapHandler(...) local callbackReturn = tapHandler(...)
if Promise.is(callbackReturn) then if Promise.is(callbackReturn) then
@ -1363,7 +1496,7 @@ end
@param ...? any -- Additional arguments which will be passed to `callback` @param ...? any -- Additional arguments which will be passed to `callback`
@return Promise @return Promise
]=] ]=]
function Promise.prototype:andThenCall(callback, ...) function Promise.prototype:andThenCall(callback: any, ...: any)
assert(isCallable(callback), string.format(ERROR_NON_FUNCTION, "Promise:andThenCall")) assert(isCallable(callback), string.format(ERROR_NON_FUNCTION, "Promise:andThenCall"))
local length, values = pack(...) local length, values = pack(...)
return self:_andThen(debug.traceback(nil, 2), function() return self:_andThen(debug.traceback(nil, 2), function()
@ -1393,7 +1526,7 @@ end
@param ... any -- Values to return from the function @param ... any -- Values to return from the function
@return Promise @return Promise
]=] ]=]
function Promise.prototype:andThenReturn(...) function Promise.prototype:andThenReturn(...: any)
local length, values = pack(...) local length, values = pack(...)
return self:_andThen(debug.traceback(nil, 2), function() return self:_andThen(debug.traceback(nil, 2), function()
return unpack(values, 1, length) return unpack(values, 1, length)
@ -1439,7 +1572,7 @@ end
Used to decrease the number of consumers by 1, and if there are no more, Used to decrease the number of consumers by 1, and if there are no more,
cancel this promise. cancel this promise.
]] ]]
function Promise.prototype:_consumerCancelled(consumer) function Promise.prototype:_consumerCancelled(consumer: any)
if self._status ~= Promise.Status.Started then if self._status ~= Promise.Status.Started then
return return
end end
@ -1455,10 +1588,10 @@ end
Used to set a handler for when the promise resolves, rejects, or is Used to set a handler for when the promise resolves, rejects, or is
cancelled. cancelled.
]] ]]
function Promise.prototype:_finally(traceback, finallyHandler) function Promise.prototype:_finally(traceback: any, finallyHandler: any)
self._unhandledRejection = false self._unhandledRejection = false
local promise = Promise._new(traceback, function(resolve, reject, onCancel) local promise = Promise._new(traceback, function(resolve: any, reject: any, onCancel: any)
local handlerPromise local handlerPromise
onCancel(function() onCancel(function()
@ -1474,7 +1607,7 @@ function Promise.prototype:_finally(traceback, finallyHandler)
local finallyCallback = resolve local finallyCallback = resolve
if finallyHandler then if finallyHandler then
finallyCallback = function(...) finallyCallback = function(...: any)
local callbackReturn = finallyHandler(...) local callbackReturn = finallyHandler(...)
if Promise.is(callbackReturn) then if Promise.is(callbackReturn) then
@ -1486,7 +1619,7 @@ function Promise.prototype:_finally(traceback, finallyHandler)
resolve(self) resolve(self)
end end
end) end)
:catch(function(...) :catch(function(...: any)
reject(...) reject(...)
end) end)
else else
@ -1556,7 +1689,7 @@ end
@param finallyHandler (status: Status) -> ...any @param finallyHandler (status: Status) -> ...any
@return Promise<...any> @return Promise<...any>
]=] ]=]
function Promise.prototype:finally(finallyHandler) function Promise.prototype:finally(finallyHandler: any)
assert(finallyHandler == nil or isCallable(finallyHandler), string.format(ERROR_NON_FUNCTION, "Promise:finally")) assert(finallyHandler == nil or isCallable(finallyHandler), string.format(ERROR_NON_FUNCTION, "Promise:finally"))
return self:_finally(debug.traceback(nil, 2), finallyHandler) return self:_finally(debug.traceback(nil, 2), finallyHandler)
end end
@ -1570,7 +1703,7 @@ end
@param ...? any -- Additional arguments which will be passed to `callback` @param ...? any -- Additional arguments which will be passed to `callback`
@return Promise @return Promise
]=] ]=]
function Promise.prototype:finallyCall(callback, ...) function Promise.prototype:finallyCall(callback: any, ...: any)
assert(isCallable(callback), string.format(ERROR_NON_FUNCTION, "Promise:finallyCall")) assert(isCallable(callback), string.format(ERROR_NON_FUNCTION, "Promise:finallyCall"))
local length, values = pack(...) local length, values = pack(...)
return self:_finally(debug.traceback(nil, 2), function() return self:_finally(debug.traceback(nil, 2), function()
@ -1596,7 +1729,7 @@ end
@param ... any -- Values to return from the function @param ... any -- Values to return from the function
@return Promise @return Promise
]=] ]=]
function Promise.prototype:finallyReturn(...) function Promise.prototype:finallyReturn(...: any)
local length, values = pack(...) local length, values = pack(...)
return self:_finally(debug.traceback(nil, 2), function() return self:_finally(debug.traceback(nil, 2), function()
return unpack(values, 1, length) return unpack(values, 1, length)
@ -1638,7 +1771,7 @@ function Promise.prototype:awaitStatus()
return self._status return self._status
end end
local function awaitHelper(status, ...) local function awaitHelper(status: any, ...: any)
return status == Promise.Status.Resolved, ... return status == Promise.Status.Resolved, ...
end end
@ -1667,7 +1800,7 @@ function Promise.prototype:await()
return awaitHelper(self:awaitStatus()) return awaitHelper(self:awaitStatus())
end end
local function expectHelper(status, ...) local function expectHelper(status: any, ...: any)
if status ~= Promise.Status.Resolved then if status ~= Promise.Status.Resolved then
error((...) == nil and "Expected Promise rejected with no value." or (...), 3) error((...) == nil and "Expected Promise rejected with no value." or (...), 3)
end end
@ -1724,7 +1857,7 @@ function Promise.prototype:_unwrap()
return success, unpack(self._values, 1, self._valuesLength) return success, unpack(self._values, 1, self._valuesLength)
end end
function Promise.prototype:_resolve(...) function Promise.prototype:_resolve(...: any)
if self._status ~= Promise.Status.Started then if self._status ~= Promise.Status.Started then
if Promise.is((...)) then if Promise.is((...)) then
(...):_consumerCancelled(self) (...):_consumerCancelled(self)
@ -1745,9 +1878,9 @@ function Promise.prototype:_resolve(...)
local chainedPromise = ... local chainedPromise = ...
local promise = chainedPromise:andThen(function(...) local promise = chainedPromise:andThen(function(...: any)
self:_resolve(...) self:_resolve(...)
end, function(...) end, function(...: any)
local maybeRuntimeError = chainedPromise._values[1] local maybeRuntimeError = chainedPromise._values[1]
-- Backwards compatibility < v2 -- Backwards compatibility < v2
@ -1771,6 +1904,7 @@ function Promise.prototype:_resolve(...)
end end
self:_reject(...) self:_reject(...)
return
end) end)
if promise._status == Promise.Status.Cancelled then if promise._status == Promise.Status.Cancelled then
@ -1795,7 +1929,7 @@ function Promise.prototype:_resolve(...)
self:_finalize() self:_finalize()
end end
function Promise.prototype:_reject(...) function Promise.prototype:_reject(...: any)
if self._status ~= Promise.Status.Started then if self._status ~= Promise.Status.Started then
return return
end end
@ -1886,10 +2020,10 @@ end
@param rejectionValue? any -- The value to reject with if the Promise isn't resolved @param rejectionValue? any -- The value to reject with if the Promise isn't resolved
@return Promise @return Promise
]=] ]=]
function Promise.prototype:now(rejectionValue) function Promise.prototype:now(rejectionValue: any)
local traceback = debug.traceback(nil, 2) local traceback = debug.traceback(nil, 2)
if self._status == Promise.Status.Resolved then if self._status == Promise.Status.Resolved then
return self:_andThen(traceback, function(...) return self:_andThen(traceback, function(...: any)
return ... return ...
end) end)
else else
@ -1931,13 +2065,13 @@ end
@param ...? P @param ...? P
@return Promise<T> @return Promise<T>
]=] ]=]
function Promise.retry(callback, times, ...) function Promise.retry(callback: any, times: any, ...: any)
assert(isCallable(callback), "Parameter #1 to Promise.retry must be a function") assert(isCallable(callback), "Parameter #1 to Promise.retry must be a function")
assert(type(times) == "number", "Parameter #2 to Promise.retry must be a number") assert(type(times) == "number", "Parameter #2 to Promise.retry must be a number")
local args, length = { ... }, select("#", ...) local args, length = { ... }, select("#", ...)
return Promise.resolve(callback(...)):catch(function(...) return Promise.resolve(callback(...)):catch(function(...: any)
if times > 0 then if times > 0 then
return Promise.retry(callback, times - 1, unpack(args, 1, length)) return Promise.retry(callback, times - 1, unpack(args, 1, length))
else else
@ -1959,14 +2093,14 @@ end
@param ...? P @param ...? P
@return Promise<T> @return Promise<T>
]=] ]=]
function Promise.retryWithDelay(callback, times, seconds, ...) function Promise.retryWithDelay(callback: any, times: any, seconds: any, ...: any)
assert(isCallable(callback), "Parameter #1 to Promise.retry must be a function") assert(isCallable(callback), "Parameter #1 to Promise.retry must be a function")
assert(type(times) == "number", "Parameter #2 (times) to Promise.retry must be a number") assert(type(times) == "number", "Parameter #2 (times) to Promise.retry must be a number")
assert(type(seconds) == "number", "Parameter #3 (seconds) to Promise.retry must be a number") assert(type(seconds) == "number", "Parameter #3 (seconds) to Promise.retry must be a number")
local args, length = { ... }, select("#", ...) local args, length = { ... }, select("#", ...)
return Promise.resolve(callback(...)):catch(function(...) return Promise.resolve(callback(...)):catch(function(...: any)
if times > 0 then if times > 0 then
Promise.delay(seconds):await() Promise.delay(seconds):await()
@ -2001,12 +2135,12 @@ end
@param predicate? (...: P) -> boolean -- A function which determines if the Promise should resolve with the given value, or wait for the next event to check again. @param predicate? (...: P) -> boolean -- A function which determines if the Promise should resolve with the given value, or wait for the next event to check again.
@return Promise<P> @return Promise<P>
]=] ]=]
function Promise.fromEvent(event, predicate) function Promise.fromEvent(event: any, predicate: any)
predicate = predicate or function() predicate = predicate or function()
return true return true
end end
return Promise._new(debug.traceback(nil, 2), function(resolve, _, onCancel) return Promise._new(debug.traceback(nil, 2), function(resolve: any, _: any, onCancel: any)
local connection local connection
local shouldDisconnect = false local shouldDisconnect = false
@ -2019,8 +2153,8 @@ function Promise.fromEvent(event, predicate)
-- Connect returns, connection will still be nil. This happens with events that queue up -- Connect returns, connection will still be nil. This happens with events that queue up
-- events when there's nothing connected, such as RemoteEvents -- events when there's nothing connected, such as RemoteEvents
connection = event:Connect(function(...) connection = event:Connect(function(...: any)
local callbackValue = predicate(...) local callbackValue = (predicate :: any)(...)
if callbackValue == true then if callbackValue == true then
resolve(...) resolve(...)
@ -2040,6 +2174,7 @@ function Promise.fromEvent(event, predicate)
end end
onCancel(disconnect) onCancel(disconnect)
return
end) end)
end end
@ -2053,7 +2188,7 @@ end
@param callback (promise: Promise, ...: any) -- A callback that runs when an unhandled rejection happens. @param callback (promise: Promise, ...: any) -- A callback that runs when an unhandled rejection happens.
@return () -> () -- Function that unregisters the `callback` when called @return () -> () -- Function that unregisters the `callback` when called
]=] ]=]
function Promise.onUnhandledRejection(callback) function Promise.onUnhandledRejection(callback: any)
table.insert(Promise._unhandledRejectionCallbacks, callback) table.insert(Promise._unhandledRejectionCallbacks, callback)
return function() return function()
@ -2065,4 +2200,4 @@ function Promise.onUnhandledRejection(callback)
end end
end end
return Promise return (Promise :: any) :: PromiseLib

View file

@ -1,8 +0,0 @@
[package]
name = "evaera/roblox-lua-promise"
version = "4.0.0"
author = "evaera"
content_root = "lib"
license = "MIT"
[dependencies]

View file

@ -1 +0,0 @@
std = "roblox+testez"

View file

@ -1,6 +1,6 @@
[package] [package]
name = "evaera/promise" name = "ambers-careware/promise"
description = "Promise implementation for Roblox" description = "Promise implementation for Roblox Luau"
version = "4.0.0" version = "4.0.0"
license = "MIT" license = "MIT"
registry = "https://github.com/UpliftGames/wally-index" registry = "https://github.com/UpliftGames/wally-index"