2018-01-26 01:21:58 +00:00
--[[
An implementation of Promises similar to Promise / A + .
] ]
2019-09-28 04:44:18 +00:00
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 "
2019-09-28 09:14:53 +00:00
local ERROR_NON_FUNCTION = " Please pass a handler function to %s! "
2019-09-28 04:44:18 +00:00
2020-03-29 10:35:15 +00:00
local MODE_KEY_METATABLE = {
__mode = " k " ;
}
2019-09-10 19:34:06 +00:00
local RunService = game : GetService ( " RunService " )
2018-01-26 01:21:58 +00:00
2020-05-06 23:22:48 +00:00
--[[
Creates an enum dictionary with some metamethods to prevent common mistakes .
] ]
local function makeEnum ( enumName , members )
local enum = { }
for _ , memberName in ipairs ( members ) do
enum [ memberName ] = memberName
end
return setmetatable ( enum , {
__index = function ( _ , k )
error ( ( " %s is not in %s! " ) : format ( k , enumName ) , 2 )
end ,
__newindex = function ( )
error ( ( " Creating new members in %s is not allowed! " ) : format ( enumName ) , 2 )
end
} )
end
2020-05-05 03:56:49 +00:00
--[[
An object to represent runtime errors that occur during execution .
Promises that experience an error like this will be rejected with
an instance of this object .
] ]
2020-05-06 23:22:48 +00:00
local Error do
Error = {
Kind = makeEnum ( " Promise.Error.Kind " , {
" ExecutionError " ,
" AlreadyCancelled " ,
" NotResolvedInTime " ,
" TimedOut "
} )
}
Error.__index = Error
function Error . new ( options , parent )
options = options or { }
return setmetatable ( {
error = tostring ( options.error ) or " [This error has no error text.] " ,
trace = options.trace ,
context = options.context ,
kind = options.kind ,
parent = parent ,
createdTick = tick ( ) ,
createdTrace = debug.traceback ( )
} , Error )
end
2020-05-05 03:56:49 +00:00
2020-05-06 23:22:48 +00:00
function Error . is ( anything )
if type ( anything ) == " table " then
local metatable = getmetatable ( anything )
2020-05-06 22:02:10 +00:00
2020-05-06 23:22:48 +00:00
if type ( metatable ) == " table " then
return rawget ( anything , " error " ) ~= nil and type ( rawget ( metatable , " extend " ) ) == " function "
end
2020-05-06 22:02:10 +00:00
end
2020-05-06 23:22:48 +00:00
return false
2020-05-05 03:56:49 +00:00
end
2020-05-06 23:22:48 +00:00
function Error . isKind ( anything , kind )
assert ( kind ~= nil , " Argument #2 to Promise.Error.isKind must not be nil " )
2020-05-05 03:56:49 +00:00
2020-05-06 23:22:48 +00:00
return Error.is ( anything ) and anything.kind == kind
end
2020-05-06 22:02:10 +00:00
2020-05-06 23:22:48 +00:00
function Error : extend ( options )
options = options or { }
2020-05-06 22:02:10 +00:00
2020-05-06 23:22:48 +00:00
options.kind = options.kind or self.kind
return Error.new ( options , self )
2020-05-06 22:02:10 +00:00
end
2020-05-06 23:22:48 +00:00
function Error : getErrorChain ( )
local runtimeErrors = { self }
2020-05-05 03:56:49 +00:00
2020-05-06 23:22:48 +00:00
while runtimeErrors [ # runtimeErrors ] . parent do
table.insert ( runtimeErrors , runtimeErrors [ # runtimeErrors ] . parent )
end
2020-05-06 22:02:10 +00:00
2020-05-06 23:22:48 +00:00
return runtimeErrors
2020-05-06 22:02:10 +00:00
end
2020-05-06 23:22:48 +00:00
function Error : __tostring ( )
local errorStrings = {
( " -- Promise.Error(%s) -- " ) : format ( self.kind or " ? " ) ,
}
for _ , runtimeError in ipairs ( self : getErrorChain ( ) ) do
table.insert ( errorStrings , table.concat ( {
runtimeError.trace or runtimeError.error ,
runtimeError.context
} , " \n " ) )
end
return table.concat ( errorStrings , " \n " )
end
2020-05-05 03:56:49 +00:00
end
2018-07-05 22:49:25 +00:00
--[[
Packs a number of arguments into a table and returns its length .
Used to cajole varargs without dropping sparse values .
] ]
local function pack ( ... )
2020-02-14 12:02:48 +00:00
return select ( " # " , ... ) , { ... }
2018-07-05 22:49:25 +00:00
end
2019-09-28 04:19:29 +00:00
--[[
Returns first value ( success ) , and packs all following values .
] ]
2020-02-14 21:08:53 +00:00
local function packResult ( success , ... )
return success , select ( " # " , ... ) , { ... }
2019-09-18 20:42:15 +00:00
end
2018-07-05 22:49:25 +00:00
2019-09-28 04:19:29 +00:00
2020-05-05 03:56:49 +00:00
local function makeErrorHandler ( traceback )
2020-05-11 19:43:35 +00:00
assert ( traceback ~= nil )
2020-05-05 03:56:49 +00:00
return function ( err )
2020-05-11 19:43:35 +00:00
-- If the error object is already a table, forward it directly.
-- Should we extend the error here and add our own trace?
if type ( err ) == " table " then
return err
end
2020-05-06 23:22:48 +00:00
return Error.new ( {
2020-05-06 22:02:10 +00:00
error = err ,
2020-05-06 23:22:48 +00:00
kind = Error.Kind . ExecutionError ,
2020-05-11 19:43:35 +00:00
trace = debug.traceback ( tostring ( err ) , 2 ) ,
2020-05-06 22:02:10 +00:00
context = " Promise created at: \n \n " .. traceback
} )
2018-01-26 01:21:58 +00:00
end
2020-05-05 03:56:49 +00:00
end
2018-05-21 21:10:38 +00:00
2020-05-05 03:56:49 +00:00
--[[
Calls a Promise executor with error handling .
] ]
local function runExecutor ( traceback , callback , ... )
return packResult ( xpcall ( callback , makeErrorHandler ( traceback ) , ... ) )
2018-01-26 01:21:58 +00:00
end
--[[
Creates a function that invokes a callback with correct error handling and
resolution mechanisms .
] ]
2019-09-28 09:14:53 +00:00
local function createAdvancer ( traceback , callback , resolve , reject )
2018-01-26 01:21:58 +00:00
return function ( ... )
2020-05-05 03:56:49 +00:00
local ok , resultLength , result = runExecutor ( traceback , callback , ... )
2018-01-26 01:21:58 +00:00
if ok then
2019-09-18 20:42:15 +00:00
resolve ( unpack ( result , 1 , resultLength ) )
2018-01-26 01:21:58 +00:00
else
2020-05-06 22:02:10 +00:00
reject ( result [ 1 ] )
2018-01-26 01:21:58 +00:00
end
end
end
local function isEmpty ( t )
return next ( t ) == nil
end
2020-05-05 03:56:49 +00:00
local Promise = {
2020-05-06 23:22:48 +00:00
Error = Error ,
Status = makeEnum ( " Promise.Status " , { " Started " , " Resolved " , " Rejected " , " Cancelled " } ) ,
2020-05-05 03:56:49 +00:00
_timeEvent = RunService.Heartbeat ,
2020-05-06 22:02:10 +00:00
_getTime = tick ,
2020-05-05 03:56:49 +00:00
}
2018-09-14 20:43:22 +00:00
Promise.prototype = { }
Promise.__index = Promise.prototype
2018-01-26 01:21:58 +00:00
--[[
Constructs a new Promise with the given initializing callback .
This is generally only called when directly wrapping a non - promise API into
a promise - based version .
The callback will receive ' resolve ' and ' reject ' methods , used to start
invoking the promise chain .
2018-10-24 06:33:30 +00:00
Second parameter , parent , is used internally for tracking the " parent " in a
promise chain . External code shouldn ' t need to worry about this.
2018-01-26 01:21:58 +00:00
] ]
2020-05-05 03:56:49 +00:00
function Promise . _new ( traceback , callback , parent )
2018-10-24 06:33:30 +00:00
if parent ~= nil and not Promise.is ( parent ) then
error ( " Argument #2 to Promise.new must be a promise or nil " , 2 )
end
2018-09-14 20:47:51 +00:00
local self = {
2018-01-26 01:21:58 +00:00
-- Used to locate where a promise was created
2020-05-05 03:56:49 +00:00
_source = traceback ,
2018-01-26 01:21:58 +00:00
_status = Promise.Status . Started ,
-- A table containing a list of all results, whether success or failure.
-- Only valid if _status is set to something besides Started
2018-06-17 02:42:14 +00:00
_values = nil ,
2018-01-26 01:21:58 +00:00
2018-06-17 02:47:21 +00:00
-- Lua doesn't like sparse arrays very much, so we explicitly store the
-- length of _values to handle middle nils.
_valuesLength = - 1 ,
2019-09-12 07:58:56 +00:00
-- Tracks if this Promise has no error observers..
_unhandledRejection = true ,
2018-01-26 01:21:58 +00:00
-- Queues representing functions we should invoke when we update!
_queuedResolve = { } ,
_queuedReject = { } ,
2018-10-24 06:33:30 +00:00
_queuedFinally = { } ,
2018-10-23 23:12:05 +00:00
-- The function to run when/if this promise is cancelled.
_cancellationHook = nil ,
2018-10-24 06:33:30 +00:00
-- The "parent" of this promise in a promise chain. Required for
2020-05-05 03:56:49 +00:00
-- cancellation propagation upstream.
2018-10-24 06:33:30 +00:00
_parent = parent ,
2020-05-05 03:56:49 +00:00
-- Consumers are Promises that have chained onto this one.
-- We track them for cancellation propagation downstream.
2020-03-29 10:35:15 +00:00
_consumers = setmetatable ( { } , MODE_KEY_METATABLE ) ,
2018-01-26 01:21:58 +00:00
}
2019-09-12 07:58:56 +00:00
if parent and parent._status == Promise.Status . Started then
parent._consumers [ self ] = true
end
2018-09-14 20:47:51 +00:00
setmetatable ( self , Promise )
2018-01-26 01:21:58 +00:00
local function resolve ( ... )
2018-09-14 20:47:51 +00:00
self : _resolve ( ... )
2018-01-26 01:21:58 +00:00
end
local function reject ( ... )
2018-09-14 20:47:51 +00:00
self : _reject ( ... )
2018-01-26 01:21:58 +00:00
end
2018-10-23 23:12:05 +00:00
local function onCancel ( cancellationHook )
2019-09-12 07:58:56 +00:00
if cancellationHook then
if self._status == Promise.Status . Cancelled then
cancellationHook ( )
else
self._cancellationHook = cancellationHook
end
2018-11-09 05:00:40 +00:00
end
2019-09-12 07:58:56 +00:00
return self._status == Promise.Status . Cancelled
2018-10-23 23:12:05 +00:00
end
2020-05-05 03:56:49 +00:00
coroutine.wrap ( function ( )
local ok , _ , result = runExecutor (
self._source ,
callback ,
resolve ,
reject ,
onCancel
)
2018-01-26 01:21:58 +00:00
2020-05-05 03:56:49 +00:00
if not ok then
2020-05-06 22:02:10 +00:00
reject ( result [ 1 ] )
2020-05-05 03:56:49 +00:00
end
end ) ( )
2018-01-26 01:21:58 +00:00
2018-09-14 20:47:51 +00:00
return self
2018-01-26 01:21:58 +00:00
end
2020-05-05 03:56:49 +00:00
function Promise . new ( executor )
return Promise._new ( debug.traceback ( nil , 2 ) , executor )
2019-09-28 09:14:53 +00:00
end
2020-05-05 03:56:49 +00:00
function Promise : __tostring ( )
return ( " Promise(%s) " ) : format ( self : getStatus ( ) )
2019-09-28 09:14:53 +00:00
end
2019-09-10 21:12:00 +00:00
--[[
2019-09-18 20:42:15 +00:00
Promise.new , except pcall on a new thread is automatic .
2019-09-10 21:12:00 +00:00
] ]
2020-05-05 03:56:49 +00:00
function Promise . defer ( callback )
local traceback = debug.traceback ( nil , 2 )
2019-09-28 09:14:53 +00:00
local promise
2020-02-12 23:55:29 +00:00
promise = Promise._new ( traceback , function ( resolve , reject , onCancel )
2019-09-18 20:42:15 +00:00
local connection
2020-05-05 03:56:49 +00:00
connection = Promise._timeEvent : Connect ( function ( )
2019-09-18 20:42:15 +00:00
connection : Disconnect ( )
2020-05-05 03:56:49 +00:00
local ok , _ , result = runExecutor ( traceback , callback , resolve , reject , onCancel )
2019-09-10 21:12:00 +00:00
2019-09-18 20:42:15 +00:00
if not ok then
2020-05-06 22:02:10 +00:00
reject ( result [ 1 ] )
2019-09-18 20:42:15 +00:00
end
end )
2019-09-10 19:34:06 +00:00
end )
2019-09-28 09:14:53 +00:00
return promise
2019-09-10 19:34:06 +00:00
end
2020-05-05 03:56:49 +00:00
-- Backwards compatibility
Promise.async = Promise.defer
2018-01-26 01:21:58 +00:00
--[[
Create a promise that represents the immediately resolved value .
] ]
2019-09-28 21:48:55 +00:00
function Promise . resolve ( ... )
local length , values = pack ( ... )
2020-05-05 03:56:49 +00:00
return Promise._new ( debug.traceback ( nil , 2 ) , function ( resolve )
2019-09-28 21:48:55 +00:00
resolve ( unpack ( values , 1 , length ) )
2018-01-26 01:21:58 +00:00
end )
end
--[[
Create a promise that represents the immediately rejected value .
] ]
2019-09-28 21:48:55 +00:00
function Promise . reject ( ... )
local length , values = pack ( ... )
2020-05-05 03:56:49 +00:00
return Promise._new ( debug.traceback ( nil , 2 ) , function ( _ , reject )
2019-09-28 21:48:55 +00:00
reject ( unpack ( values , 1 , length ) )
2018-01-26 01:21:58 +00:00
end )
end
2020-05-05 03:56:49 +00:00
--[[
Runs a non - promise - returning function as a Promise with the
given arguments .
] ]
function Promise . _try ( traceback , callback , ... )
local valuesLength , values = pack ( ... )
return Promise._new ( traceback , function ( resolve , reject )
resolve ( callback ( unpack ( values , 1 , valuesLength ) ) )
end )
end
2019-09-28 22:54:49 +00:00
--[[
Begins a Promise chain , turning synchronous errors into rejections .
] ]
function Promise . try ( ... )
2020-05-05 03:56:49 +00:00
return Promise._try ( debug.traceback ( nil , 2 ) , ... )
2019-09-28 22:54:49 +00:00
end
2018-01-26 01:21:58 +00:00
--[[
Returns a new promise that :
* is resolved when all input promises resolve
* is rejected if ANY input promises reject
] ]
2019-09-29 06:03:22 +00:00
function Promise . _all ( traceback , promises , amount )
2018-09-14 18:17:06 +00:00
if type ( promises ) ~= " table " then
2019-09-29 06:03:22 +00:00
error ( ERROR_NON_LIST : format ( " Promise.all " ) , 3 )
2018-09-14 18:17:06 +00:00
end
2018-09-14 20:47:10 +00:00
-- We need to check that each value is a promise here so that we can produce
-- a proper error rather than a rejected promise with our error.
2020-02-29 02:21:01 +00:00
for i , promise in pairs ( promises ) do
2019-09-27 22:46:10 +00:00
if not Promise.is ( promise ) then
2019-09-29 06:03:22 +00:00
error ( ( ERROR_NON_PROMISE_IN_LIST ) : format ( " Promise.all " , tostring ( i ) ) , 3 )
2018-09-14 18:17:06 +00:00
end
end
2019-09-27 22:46:10 +00:00
-- If there are no values then return an already resolved promise.
2019-09-29 06:03:22 +00:00
if # promises == 0 or amount == 0 then
2019-09-27 22:46:10 +00:00
return Promise.resolve ( { } )
end
2020-05-05 03:56:49 +00:00
return Promise._new ( traceback , function ( resolve , reject , onCancel )
2018-09-14 18:17:06 +00:00
-- An array to contain our resolved values from the given promises.
local resolvedValues = { }
2019-09-28 04:13:30 +00:00
local newPromises = { }
2018-09-14 20:47:10 +00:00
-- Keep a count of resolved promises because just checking the resolved
-- values length wouldn't account for promises that resolve with nil.
2018-09-14 18:17:06 +00:00
local resolvedCount = 0
2019-09-29 06:03:22 +00:00
local rejectedCount = 0
local done = false
local function cancel ( )
for _ , promise in ipairs ( newPromises ) do
promise : cancel ( )
end
end
2018-09-14 18:17:06 +00:00
-- Called when a single value is resolved and resolves if all are done.
local function resolveOne ( i , ... )
2019-09-29 06:03:22 +00:00
if done then
return
end
2018-09-14 18:17:06 +00:00
resolvedCount = resolvedCount + 1
2019-09-29 06:03:22 +00:00
if amount == nil then
resolvedValues [ i ] = ...
else
resolvedValues [ resolvedCount ] = ...
end
if resolvedCount >= ( amount or # promises ) then
done = true
2018-09-14 18:17:06 +00:00
resolve ( resolvedValues )
2019-09-29 06:03:22 +00:00
cancel ( )
2018-09-14 18:17:06 +00:00
end
end
2019-09-29 06:03:22 +00:00
onCancel ( cancel )
2018-09-14 20:47:10 +00:00
-- We can assume the values inside `promises` are all promises since we
-- checked above.
2020-02-14 12:02:48 +00:00
for i , promise in ipairs ( promises ) do
2019-09-28 04:13:30 +00:00
table.insert (
newPromises ,
2020-02-14 12:02:48 +00:00
promise : andThen (
2019-09-28 04:13:30 +00:00
function ( ... )
resolveOne ( i , ... )
end ,
function ( ... )
2019-09-29 06:03:22 +00:00
rejectedCount = rejectedCount + 1
if amount == nil or # promises - rejectedCount < amount then
cancel ( )
done = true
reject ( ... )
2019-09-28 04:13:30 +00:00
end
2019-09-29 06:03:22 +00:00
end
)
)
end
if done then
cancel ( )
end
end )
end
function Promise . all ( promises )
2020-05-05 03:56:49 +00:00
return Promise._all ( debug.traceback ( nil , 2 ) , promises )
2019-09-29 06:03:22 +00:00
end
function Promise . some ( promises , amount )
assert ( type ( amount ) == " number " , " Bad argument #2 to Promise.some: must be a number " )
2020-05-05 03:56:49 +00:00
return Promise._all ( debug.traceback ( nil , 2 ) , promises , amount )
2019-09-29 06:03:22 +00:00
end
function Promise . any ( promises )
2020-05-05 03:56:49 +00:00
return Promise._all ( debug.traceback ( nil , 2 ) , promises , 1 ) : andThen ( function ( values )
2019-09-29 06:03:22 +00:00
return values [ 1 ]
end )
end
function Promise . allSettled ( promises )
if type ( promises ) ~= " table " then
error ( ERROR_NON_LIST : format ( " Promise.allSettled " ) , 2 )
end
-- We need to check that each value is a promise here so that we can produce
-- a proper error rather than a rejected promise with our error.
2020-02-29 02:21:01 +00:00
for i , promise in pairs ( promises ) do
2019-09-29 06:03:22 +00:00
if not Promise.is ( promise ) then
error ( ( ERROR_NON_PROMISE_IN_LIST ) : format ( " Promise.allSettled " , tostring ( i ) ) , 2 )
end
end
-- If there are no values then return an already resolved promise.
if # promises == 0 then
return Promise.resolve ( { } )
end
2020-05-05 03:56:49 +00:00
return Promise._new ( debug.traceback ( nil , 2 ) , function ( resolve , _ , onCancel )
2019-09-29 06:03:22 +00:00
-- An array to contain our resolved values from the given promises.
local fates = { }
local newPromises = { }
-- Keep a count of resolved promises because just checking the resolved
-- values length wouldn't account for promises that resolve with nil.
local finishedCount = 0
-- Called when a single value is resolved and resolves if all are done.
local function resolveOne ( i , ... )
finishedCount = finishedCount + 1
fates [ i ] = ...
2019-09-28 04:13:30 +00:00
2019-09-29 06:03:22 +00:00
if finishedCount >= # promises then
resolve ( fates )
end
end
onCancel ( function ( )
for _ , promise in ipairs ( newPromises ) do
promise : cancel ( )
end
end )
-- We can assume the values inside `promises` are all promises since we
-- checked above.
2020-02-14 12:02:48 +00:00
for i , promise in ipairs ( promises ) do
2019-09-29 06:03:22 +00:00
table.insert (
newPromises ,
2020-02-14 12:02:48 +00:00
promise : finally (
2019-09-29 06:03:22 +00:00
function ( ... )
resolveOne ( i , ... )
2019-09-28 04:13:30 +00:00
end
)
2018-09-14 18:31:24 +00:00
)
2018-09-14 18:17:06 +00:00
end
end )
2018-01-26 01:21:58 +00:00
end
2019-09-10 19:34:06 +00:00
--[[
Races a set of Promises and returns the first one that resolves ,
cancelling the others .
] ]
function Promise . race ( promises )
2019-09-28 04:44:18 +00:00
assert ( type ( promises ) == " table " , ERROR_NON_LIST : format ( " Promise.race " ) )
2019-09-10 19:34:06 +00:00
2020-02-29 02:21:01 +00:00
for i , promise in pairs ( promises ) do
2019-09-28 04:44:18 +00:00
assert ( Promise.is ( promise ) , ( ERROR_NON_PROMISE_IN_LIST ) : format ( " Promise.race " , tostring ( i ) ) )
2019-09-10 19:34:06 +00:00
end
2020-05-05 03:56:49 +00:00
return Promise._new ( debug.traceback ( nil , 2 ) , function ( resolve , reject , onCancel )
2019-09-28 04:13:30 +00:00
local newPromises = { }
2019-09-29 06:03:22 +00:00
local finished = false
local function cancel ( )
for _ , promise in ipairs ( newPromises ) do
promise : cancel ( )
end
end
2019-09-28 04:13:30 +00:00
2019-09-10 19:34:06 +00:00
local function finalize ( callback )
return function ( ... )
2019-09-29 06:03:22 +00:00
cancel ( )
finished = true
2019-09-10 19:34:06 +00:00
return callback ( ... )
end
end
2019-09-28 04:13:30 +00:00
if onCancel ( finalize ( reject ) ) then
return
end
2019-09-10 19:34:06 +00:00
for _ , promise in ipairs ( promises ) do
2019-09-28 04:13:30 +00:00
table.insert (
newPromises ,
promise : andThen ( finalize ( resolve ) , finalize ( reject ) )
)
2019-09-10 19:34:06 +00:00
end
2019-09-29 06:03:22 +00:00
if finished then
cancel ( )
end
2019-09-10 19:34:06 +00:00
end )
end
2020-05-13 23:48:45 +00:00
--[[
Iterates serially over the given an array of values , calling the predicate callback on each before continuing .
If the predicate returns a Promise , we wait for that Promise to resolve before continuing to the next item
in the array . If the Promise the predicate returns rejects , the Promise from Promise.each is also rejected with
the same value .
Returns a Promise containing an array of the return values from the predicate for each item in the original list .
] ]
function Promise . each ( list , predicate )
assert ( type ( list ) == " table " , ERROR_NON_LIST : format ( " Promise.each " ) )
assert ( type ( predicate ) == " function " , ERROR_NON_FUNCTION : format ( " Promise.each " ) )
return Promise._new ( debug.traceback ( nil , 2 ) , function ( resolve , reject , onCancel )
local results = { }
local promisesToCancel = { }
local cancelled = false
local function cancel ( )
for _ , promiseToCancel in ipairs ( promisesToCancel ) do
promiseToCancel : cancel ( )
end
end
onCancel ( function ( )
cancelled = true
cancel ( )
end )
-- We need to preprocess the list of values and look for Promises.
-- If we find some, we must register our andThen calls now, so that those Promises have a consumer
-- from us registered. If we don't do this, those Promises might get cancelled by something else
-- before we get to them in the series because it's not possible to tell that we plan to use it
-- unless we indicate it here.
local preprocessedList = { }
for index , value in ipairs ( list ) do
if Promise.is ( value ) then
if value : getStatus ( ) == Promise.Status . Cancelled then
cancel ( )
return reject ( Error.new ( {
error = " Promise is cancelled " ,
kind = Error.Kind . AlreadyCancelled ,
context = ( " The Promise that was part of the array at index %d passed into Promise.each was already cancelled when Promise.each began. \n \n That Promise was created at: \n \n %s " ) : format (
index ,
value._source
)
} ) )
elseif value : getStatus ( ) == Promise.Status . Rejected then
cancel ( )
return reject ( select ( 2 , value : await ( ) ) )
end
-- Chain a new Promise from this one so we only cancel ours
local ourPromise = value : andThen ( function ( ... )
return ...
end )
table.insert ( promisesToCancel , ourPromise )
preprocessedList [ index ] = ourPromise
else
preprocessedList [ index ] = value
end
end
for index , value in ipairs ( preprocessedList ) do
if Promise.is ( value ) then
local success
success , value = value : await ( )
if not success then
cancel ( )
return reject ( value )
end
end
if cancelled then
return
end
local predicatePromise = Promise.resolve ( predicate ( value , index ) )
table.insert ( promisesToCancel , predicatePromise )
local success , result = predicatePromise : await ( )
if not success then
cancel ( )
return reject ( result )
end
results [ index ] = result
end
resolve ( results )
end )
end
2018-01-26 01:21:58 +00:00
--[[
Is the given object a Promise instance ?
] ]
function Promise . is ( object )
if type ( object ) ~= " table " then
return false
end
2020-05-05 03:56:49 +00:00
local objectMetatable = getmetatable ( object )
if objectMetatable == Promise then
-- The Promise came from this library.
return true
elseif objectMetatable == nil then
-- No metatable, but we should still chain onto tables with andThen methods
return type ( object.andThen ) == " function "
elseif type ( objectMetatable ) == " table " and type ( rawget ( objectMetatable , " andThen " ) ) == " function " then
-- Maybe this came from a different or older Promise library.
return true
end
return false
2018-01-26 01:21:58 +00:00
end
2019-09-12 07:58:56 +00:00
--[[
Converts a yielding function into a Promise - returning one .
] ]
2019-09-14 02:58:32 +00:00
function Promise . promisify ( callback )
2019-09-12 07:58:56 +00:00
return function ( ... )
2020-05-05 03:56:49 +00:00
return Promise._try ( debug.traceback ( nil , 2 ) , callback , ... )
2019-09-12 07:58:56 +00:00
end
end
2019-09-29 04:07:32 +00:00
--[[
Creates a Promise that resolves after given number of seconds .
] ]
do
2020-02-14 12:02:48 +00:00
-- uses a sorted doubly linked list (queue) to achieve O(1) remove operations and O(n) for insert
2019-09-29 04:07:32 +00:00
2020-02-14 12:02:48 +00:00
-- the initial node in the linked list
local first
local connection
2019-09-29 04:07:32 +00:00
function Promise . delay ( seconds )
assert ( type ( seconds ) == " number " , " Bad argument #1 to Promise.delay, must be a number. " )
2020-02-14 12:02:48 +00:00
-- If seconds is -INF, INF, NaN, or less than 1 / 60, assume seconds is 1 / 60.
2019-09-29 04:07:32 +00:00
-- This mirrors the behavior of wait()
2020-02-14 12:02:48 +00:00
if not ( seconds >= 1 / 60 ) or seconds == math.huge then
seconds = 1 / 60
2019-09-29 04:07:32 +00:00
end
2020-05-05 03:56:49 +00:00
return Promise._new ( debug.traceback ( nil , 2 ) , function ( resolve , _ , onCancel )
local startTime = Promise._getTime ( )
2020-02-14 12:02:48 +00:00
local endTime = startTime + seconds
local node = {
resolve = resolve ,
startTime = startTime ,
endTime = endTime
}
if connection == nil then -- first is nil when connection is nil
first = node
2020-05-05 03:56:49 +00:00
connection = Promise._timeEvent : Connect ( function ( )
local currentTime = Promise._getTime ( )
2020-02-14 12:02:48 +00:00
while first.endTime <= currentTime do
2020-05-11 19:43:50 +00:00
-- Don't use currentTime here, as this is the time when we started resolving,
-- not necessarily the time *right now*.
first.resolve ( Promise._getTime ( ) - first.startTime )
2020-02-14 12:02:48 +00:00
first = first.next
if first == nil then
connection : Disconnect ( )
connection = nil
break
end
first.previous = nil
end
end )
else -- first is non-nil
if first.endTime < endTime then -- if `node` should be placed after `first`
-- we will insert `node` between `current` and `next`
-- (i.e. after `current` if `next` is nil)
local current = first
local next = current.next
while next ~= nil and next.endTime < endTime do
current = next
next = current.next
end
-- `current` must be non-nil, but `next` could be `nil` (i.e. last item in list)
current.next = node
node.previous = current
if next ~= nil then
node.next = next
next.previous = node
end
else
-- set `node` to `first`
node.next = first
first.previous = node
first = node
end
end
2019-09-29 04:07:32 +00:00
onCancel ( function ( )
2020-02-14 12:02:48 +00:00
-- remove node from queue
local next = node.next
if first == node then
if next == nil then -- if `node` is the first and last
connection : Disconnect ( )
connection = nil
else -- if `node` is `first` and not the last
next.previous = nil
end
first = next
else
local previous = node.previous
-- since `node` is not `first`, then we know `previous` is non-nil
previous.next = next
if next ~= nil then
next.previous = previous
end
end
2019-09-29 04:07:32 +00:00
end )
end )
end
end
--[[
Rejects the promise after ` seconds ` seconds .
] ]
2020-05-06 23:22:48 +00:00
function Promise . prototype : timeout ( seconds , rejectionValue )
local traceback = debug.traceback ( nil , 2 )
2019-09-29 04:07:32 +00:00
return Promise.race ( {
Promise.delay ( seconds ) : andThen ( function ( )
2020-05-06 23:22:48 +00:00
return Promise.reject ( rejectionValue == nil and Error.new ( {
kind = Error.Kind . TimedOut ,
error = " Timed out " ,
context = ( " Timeout of %d seconds exceeded. \n :timeout() called at: \n \n %s " ) : format (
seconds ,
traceback
)
} ) or rejectionValue )
2019-09-29 04:07:32 +00:00
end ) ,
self
} )
end
2018-09-14 20:43:22 +00:00
function Promise . prototype : getStatus ( )
2018-06-17 02:40:57 +00:00
return self._status
end
2018-01-26 01:21:58 +00:00
--[[
Creates a new promise that receives the result of this promise .
The given callbacks are invoked depending on that result .
] ]
2019-09-28 09:14:53 +00:00
function Promise . prototype : _andThen ( traceback , successHandler , failureHandler )
2018-01-26 01:21:58 +00:00
self._unhandledRejection = false
-- Create a new promise to follow this part of the chain
2019-09-28 09:14:53 +00:00
return Promise._new ( traceback , function ( resolve , reject )
2018-01-26 01:21:58 +00:00
-- Our default callbacks just pass values onto the next promise.
-- This lets success and failure cascade correctly!
local successCallback = resolve
if successHandler then
2019-09-28 09:14:53 +00:00
successCallback = createAdvancer (
traceback ,
successHandler ,
resolve ,
reject
)
2018-01-26 01:21:58 +00:00
end
local failureCallback = reject
if failureHandler then
2019-09-28 09:14:53 +00:00
failureCallback = createAdvancer (
traceback ,
failureHandler ,
resolve ,
reject
)
2018-01-26 01:21:58 +00:00
end
if self._status == Promise.Status . Started then
2018-04-11 22:26:54 +00:00
-- If we haven't resolved yet, put ourselves into the queue
2018-01-26 01:21:58 +00:00
table.insert ( self._queuedResolve , successCallback )
table.insert ( self._queuedReject , failureCallback )
elseif self._status == Promise.Status . Resolved then
-- This promise has already resolved! Trigger success immediately.
2018-06-17 03:04:01 +00:00
successCallback ( unpack ( self._values , 1 , self._valuesLength ) )
2018-01-26 01:21:58 +00:00
elseif self._status == Promise.Status . Rejected then
-- This promise died a terrible death! Trigger failure immediately.
2018-06-17 03:04:01 +00:00
failureCallback ( unpack ( self._values , 1 , self._valuesLength ) )
2018-10-24 06:33:30 +00:00
elseif self._status == Promise.Status . Cancelled then
-- We don't want to call the success handler or the failure handler,
-- we just reject this promise outright.
2020-05-06 23:22:48 +00:00
reject ( Error.new ( {
error = " Promise is cancelled " ,
kind = Error.Kind . AlreadyCancelled ,
context = " Promise created at \n \n " .. traceback
} ) )
2018-01-26 01:21:58 +00:00
end
2018-10-24 06:33:30 +00:00
end , self )
2018-01-26 01:21:58 +00:00
end
2019-09-28 09:14:53 +00:00
function Promise . prototype : andThen ( successHandler , failureHandler )
assert (
successHandler == nil or type ( successHandler ) == " function " ,
ERROR_NON_FUNCTION : format ( " Promise:andThen " )
)
assert (
failureHandler == nil or type ( failureHandler ) == " function " ,
ERROR_NON_FUNCTION : format ( " Promise:andThen " )
)
2020-05-05 03:56:49 +00:00
return self : _andThen ( debug.traceback ( nil , 2 ) , successHandler , failureHandler )
2019-09-28 09:14:53 +00:00
end
2018-01-26 01:21:58 +00:00
--[[
Used to catch any errors that may have occurred in the promise .
] ]
2018-09-14 20:43:22 +00:00
function Promise . prototype : catch ( failureCallback )
2019-09-28 09:14:53 +00:00
assert (
failureCallback == nil or type ( failureCallback ) == " function " ,
ERROR_NON_FUNCTION : format ( " Promise:catch " )
)
2020-05-05 03:56:49 +00:00
return self : _andThen ( debug.traceback ( nil , 2 ) , nil , failureCallback )
2018-01-26 01:21:58 +00:00
end
2019-09-28 05:33:06 +00:00
--[[
Like andThen , but the value passed into the handler is also the
value returned from the handler .
] ]
function Promise . prototype : tap ( tapCallback )
2019-09-28 09:14:53 +00:00
assert ( type ( tapCallback ) == " function " , ERROR_NON_FUNCTION : format ( " Promise:tap " ) )
2020-05-05 03:56:49 +00:00
return self : _andThen ( debug.traceback ( nil , 2 ) , function ( ... )
2019-09-28 05:33:06 +00:00
local callbackReturn = tapCallback ( ... )
if Promise.is ( callbackReturn ) then
local length , values = pack ( ... )
return callbackReturn : andThen ( function ( )
return unpack ( values , 1 , length )
end )
end
return ...
end )
end
2019-09-13 00:13:29 +00:00
--[[
Calls a callback on ` andThen ` with specific arguments .
] ]
function Promise . prototype : andThenCall ( callback , ... )
2019-09-28 09:14:53 +00:00
assert ( type ( callback ) == " function " , ERROR_NON_FUNCTION : format ( " Promise:andThenCall " ) )
2019-09-13 00:13:29 +00:00
local length , values = pack ( ... )
2020-05-05 03:56:49 +00:00
return self : _andThen ( debug.traceback ( nil , 2 ) , function ( )
2019-09-13 00:13:29 +00:00
return callback ( unpack ( values , 1 , length ) )
end )
end
2019-09-29 02:03:06 +00:00
--[[
Shorthand for an andThen handler that returns the given value .
] ]
function Promise . prototype : andThenReturn ( ... )
local length , values = pack ( ... )
2020-05-05 03:56:49 +00:00
return self : _andThen ( debug.traceback ( nil , 2 ) , function ( )
2019-09-29 02:03:06 +00:00
return unpack ( values , 1 , length )
end )
end
2018-10-23 23:12:05 +00:00
--[[
Cancels the promise , disallowing it from rejecting or resolving , and calls
the cancellation hook if provided .
] ]
function Promise . prototype : cancel ( )
if self._status ~= Promise.Status . Started then
return
end
self._status = Promise.Status . Cancelled
if self._cancellationHook then
self._cancellationHook ( )
end
2018-10-24 06:33:30 +00:00
if self._parent then
2019-09-12 07:58:56 +00:00
self._parent : _consumerCancelled ( self )
end
for child in pairs ( self._consumers ) do
child : cancel ( )
2018-10-24 06:33:30 +00:00
end
self : _finalize ( )
2018-10-23 23:12:05 +00:00
end
2018-10-23 23:14:29 +00:00
--[[
2018-10-24 06:33:30 +00:00
Used to decrease the number of consumers by 1 , and if there are no more ,
cancel this promise .
2018-10-23 23:14:29 +00:00
] ]
2019-09-12 07:58:56 +00:00
function Promise . prototype : _consumerCancelled ( consumer )
if self._status ~= Promise.Status . Started then
return
end
2018-10-24 06:33:30 +00:00
2019-09-12 07:58:56 +00:00
self._consumers [ consumer ] = nil
if next ( self._consumers ) == nil then
2018-10-24 06:33:30 +00:00
self : cancel ( )
end
end
--[[
Used to set a handler for when the promise resolves , rejects , or is
cancelled . Returns a new promise chained from this promise .
] ]
2019-09-29 02:03:06 +00:00
function Promise . prototype : _finally ( traceback , finallyHandler , onlyOk )
if not onlyOk then
self._unhandledRejection = false
end
2018-10-24 06:33:30 +00:00
-- Return a promise chained off of this promise
2019-09-28 09:14:53 +00:00
return Promise._new ( traceback , function ( resolve , reject )
2018-10-24 06:33:30 +00:00
local finallyCallback = resolve
if finallyHandler then
2019-09-28 09:14:53 +00:00
finallyCallback = createAdvancer (
traceback ,
finallyHandler ,
resolve ,
reject
)
2018-10-24 06:33:30 +00:00
end
2019-09-29 02:03:06 +00:00
if onlyOk then
local callback = finallyCallback
finallyCallback = function ( ... )
if self._status == Promise.Status . Rejected then
return resolve ( self )
end
return callback ( ... )
end
end
2018-10-24 06:33:30 +00:00
if self._status == Promise.Status . Started then
-- The promise is not settled, so queue this.
table.insert ( self._queuedFinally , finallyCallback )
else
-- The promise already settled or was cancelled, run the callback now.
2019-09-12 07:58:56 +00:00
finallyCallback ( self._status )
2018-10-24 06:33:30 +00:00
end
end , self )
2018-10-23 23:14:29 +00:00
end
2019-09-28 09:14:53 +00:00
function Promise . prototype : finally ( finallyHandler )
assert (
finallyHandler == nil or type ( finallyHandler ) == " function " ,
ERROR_NON_FUNCTION : format ( " Promise:finally " )
)
2020-05-05 03:56:49 +00:00
return self : _finally ( debug.traceback ( nil , 2 ) , finallyHandler )
2019-09-28 09:14:53 +00:00
end
2019-09-13 00:13:29 +00:00
--[[
Calls a callback on ` finally ` with specific arguments .
] ]
function Promise . prototype : finallyCall ( callback , ... )
2019-09-28 09:14:53 +00:00
assert ( type ( callback ) == " function " , ERROR_NON_FUNCTION : format ( " Promise:finallyCall " ) )
2019-09-13 00:13:29 +00:00
local length , values = pack ( ... )
2020-05-05 03:56:49 +00:00
return self : _finally ( debug.traceback ( nil , 2 ) , function ( )
2019-09-13 00:13:29 +00:00
return callback ( unpack ( values , 1 , length ) )
end )
end
2019-09-29 02:03:06 +00:00
--[[
Shorthand for a finally handler that returns the given value .
] ]
function Promise . prototype : finallyReturn ( ... )
local length , values = pack ( ... )
2020-05-05 03:56:49 +00:00
return self : _finally ( debug.traceback ( nil , 2 ) , function ( )
2019-09-29 02:03:06 +00:00
return unpack ( values , 1 , length )
end )
end
--[[
Similar to finally , except rejections are propagated through it .
] ]
function Promise . prototype : done ( finallyHandler )
assert (
finallyHandler == nil or type ( finallyHandler ) == " function " ,
ERROR_NON_FUNCTION : format ( " Promise:finallyO " )
)
2020-05-05 03:56:49 +00:00
return self : _finally ( debug.traceback ( nil , 2 ) , finallyHandler , true )
2019-09-29 02:03:06 +00:00
end
--[[
Calls a callback on ` done ` with specific arguments .
] ]
function Promise . prototype : doneCall ( callback , ... )
assert ( type ( callback ) == " function " , ERROR_NON_FUNCTION : format ( " Promise:doneCall " ) )
local length , values = pack ( ... )
2020-05-05 03:56:49 +00:00
return self : _finally ( debug.traceback ( nil , 2 ) , function ( )
2019-09-29 02:03:06 +00:00
return callback ( unpack ( values , 1 , length ) )
end , true )
end
--[[
Shorthand for a done handler that returns the given value .
] ]
function Promise . prototype : doneReturn ( ... )
local length , values = pack ( ... )
2020-05-05 03:56:49 +00:00
return self : _finally ( debug.traceback ( nil , 2 ) , function ( )
2019-09-29 02:03:06 +00:00
return unpack ( values , 1 , length )
end , true )
end
2019-09-13 00:13:29 +00:00
2018-01-26 01:21:58 +00:00
--[[
Yield until the promise is completed .
This matches the execution model of normal Roblox functions .
] ]
2019-09-12 07:58:56 +00:00
function Promise . prototype : awaitStatus ( )
2018-01-26 01:21:58 +00:00
self._unhandledRejection = false
if self._status == Promise.Status . Started then
2018-11-09 08:40:40 +00:00
local bindable = Instance.new ( " BindableEvent " )
self : finally ( function ( )
2019-09-12 07:58:56 +00:00
bindable : Fire ( )
2018-11-09 04:50:07 +00:00
end )
2019-09-12 07:58:56 +00:00
bindable.Event : Wait ( )
2018-11-09 08:40:40 +00:00
bindable : Destroy ( )
2019-09-12 07:58:56 +00:00
end
2018-11-09 04:50:07 +00:00
2019-09-12 07:58:56 +00:00
if self._status == Promise.Status . Resolved then
return self._status , unpack ( self._values , 1 , self._valuesLength )
2018-01-26 01:21:58 +00:00
elseif self._status == Promise.Status . Rejected then
2019-09-12 07:58:56 +00:00
return self._status , unpack ( self._values , 1 , self._valuesLength )
2018-01-26 01:21:58 +00:00
end
2018-11-09 04:50:07 +00:00
2019-09-12 07:58:56 +00:00
return self._status
end
2020-02-14 12:02:48 +00:00
local function awaitHelper ( status , ... )
return status == Promise.Status . Resolved , ...
end
2019-09-12 07:58:56 +00:00
--[[
Calls awaitStatus internally , returns ( isResolved , values ... )
] ]
2020-05-06 18:20:34 +00:00
function Promise . prototype : await ( )
return awaitHelper ( self : awaitStatus ( ) )
2020-02-14 12:02:48 +00:00
end
local function expectHelper ( status , ... )
if status ~= Promise.Status . Resolved then
2020-05-06 18:20:34 +00:00
error ( ( ... ) == nil and " Expected Promise rejected with no value. " or ( ... ) , 3 )
2020-02-14 12:02:48 +00:00
end
2019-09-12 07:58:56 +00:00
2020-02-14 12:02:48 +00:00
return ...
2018-01-26 01:21:58 +00:00
end
2019-09-13 00:13:29 +00:00
--[[
Calls await and only returns if the Promise resolves .
Throws if the Promise rejects or gets cancelled .
] ]
2020-05-06 18:20:34 +00:00
function Promise . prototype : expect ( )
return expectHelper ( self : awaitStatus ( ) )
2019-09-13 00:13:29 +00:00
end
2020-05-05 03:56:49 +00:00
-- Backwards compatibility
2019-11-13 04:14:18 +00:00
Promise.prototype . awaitValue = Promise.prototype . expect
2018-09-14 18:31:24 +00:00
--[[
Intended for use in tests .
Similar to await ( ) , but instead of yielding if the promise is unresolved ,
_unwrap will throw . This indicates an assumption that a promise has
resolved .
] ]
2018-09-14 20:43:22 +00:00
function Promise . prototype : _unwrap ( )
2018-09-14 18:31:24 +00:00
if self._status == Promise.Status . Started then
error ( " Promise has not resolved or rejected. " , 2 )
end
local success = self._status == Promise.Status . Resolved
return success , unpack ( self._values , 1 , self._valuesLength )
end
2018-09-14 20:43:22 +00:00
function Promise . prototype : _resolve ( ... )
2018-01-26 01:21:58 +00:00
if self._status ~= Promise.Status . Started then
2019-09-12 07:58:56 +00:00
if Promise.is ( ( ... ) ) then
( ... ) : _consumerCancelled ( self )
end
2018-01-26 01:21:58 +00:00
return
end
-- If the resolved value was a Promise, we chain onto it!
if Promise.is ( ( ... ) ) then
-- Without this warning, arguments sometimes mysteriously disappear
2018-09-14 20:41:32 +00:00
if select ( " # " , ... ) > 1 then
2018-01-26 01:21:58 +00:00
local message = (
" When returning a Promise from andThen, extra arguments are " ..
" discarded! See: \n \n %s "
) : format (
self._source
)
warn ( message )
end
2019-09-28 09:14:53 +00:00
local chainedPromise = ...
local promise = chainedPromise : andThen (
2018-09-14 20:38:52 +00:00
function ( ... )
self : _resolve ( ... )
end ,
function ( ... )
2020-05-06 22:02:10 +00:00
local maybeRuntimeError = chainedPromise._values [ 1 ]
2020-05-05 03:56:49 +00:00
2020-05-06 22:02:10 +00:00
-- Backwards compatibility < v2
2019-09-28 09:14:53 +00:00
if chainedPromise._error then
2020-05-06 23:22:48 +00:00
maybeRuntimeError = Error.new ( {
2020-05-06 22:02:10 +00:00
error = chainedPromise._error ,
2020-05-06 23:22:48 +00:00
kind = Error.Kind . ExecutionError ,
2020-05-06 22:02:10 +00:00
context = " [No stack trace available as this Promise originated from an older version of the Promise library (< v2)] "
} )
2020-05-05 03:56:49 +00:00
end
2020-05-06 23:22:48 +00:00
if Error.isKind ( maybeRuntimeError , Error.Kind . ExecutionError ) then
2020-05-06 22:02:10 +00:00
return self : _reject ( maybeRuntimeError : extend ( {
error = " This Promise was chained to a Promise that errored. " ,
trace = " " ,
context = ( " The Promise at: \n \n %s \n ...Rejected because it was chained to the following Promise, which encountered an error: \n " ) : format (
self._source
)
} ) )
2019-09-28 09:14:53 +00:00
end
2020-05-05 03:56:49 +00:00
2018-09-14 20:38:52 +00:00
self : _reject ( ... )
end
)
2018-01-26 01:21:58 +00:00
2019-09-12 07:58:56 +00:00
if promise._status == Promise.Status . Cancelled then
self : cancel ( )
elseif promise._status == Promise.Status . Started then
-- Adopt ourselves into promise for cancellation propagation.
self._parent = promise
promise._consumers [ self ] = true
end
2018-01-26 01:21:58 +00:00
return
end
self._status = Promise.Status . Resolved
2018-09-14 20:41:32 +00:00
self._valuesLength , self._values = pack ( ... )
2018-01-26 01:21:58 +00:00
-- We assume that these callbacks will not throw errors.
for _ , callback in ipairs ( self._queuedResolve ) do
callback ( ... )
end
2018-10-24 06:33:30 +00:00
self : _finalize ( )
2018-01-26 01:21:58 +00:00
end
2018-09-14 20:43:22 +00:00
function Promise . prototype : _reject ( ... )
2018-01-26 01:21:58 +00:00
if self._status ~= Promise.Status . Started then
return
end
self._status = Promise.Status . Rejected
2018-09-14 20:38:52 +00:00
self._valuesLength , self._values = pack ( ... )
2018-01-26 01:21:58 +00:00
-- If there are any rejection handlers, call those!
if not isEmpty ( self._queuedReject ) then
-- We assume that these callbacks will not throw errors.
for _ , callback in ipairs ( self._queuedReject ) do
callback ( ... )
end
else
-- At this point, no one was able to observe the error.
-- An error handler might still be attached if the error occurred
-- synchronously. We'll wait one tick, and if there are still no
-- observers, then we should put a message in the console.
local err = tostring ( ( ... ) )
2019-09-18 21:58:07 +00:00
coroutine.wrap ( function ( )
2020-05-05 03:56:49 +00:00
Promise._timeEvent : Wait ( )
2019-09-18 21:58:07 +00:00
2018-01-26 01:21:58 +00:00
-- Someone observed the error, hooray!
if not self._unhandledRejection then
return
end
-- Build a reasonable message
2020-05-11 19:43:58 +00:00
local message = ( " Unhandled Promise rejection: \n \n %s \n \n %s " ) : format (
2020-05-05 03:56:49 +00:00
err ,
self._source
)
if Promise.TEST then
-- Don't spam output when we're running tests.
return
2019-09-28 09:14:53 +00:00
end
2020-05-05 03:56:49 +00:00
2018-01-26 01:21:58 +00:00
warn ( message )
2019-09-18 21:58:07 +00:00
end ) ( )
2018-01-26 01:21:58 +00:00
end
2018-10-24 06:33:30 +00:00
self : _finalize ( )
end
--[[
Calls any : finally handlers . We need this to be a separate method and
queue because we must call all of the finally callbacks upon a success ,
failure , * and * cancellation .
] ]
function Promise . prototype : _finalize ( )
for _ , callback in ipairs ( self._queuedFinally ) do
-- Purposefully not passing values to callbacks here, as it could be the
-- resolved values, or rejected errors. If the developer needs the values,
-- they should use :andThen or :catch explicitly.
2019-09-12 07:58:56 +00:00
callback ( self._status )
2018-10-24 06:33:30 +00:00
end
2019-09-12 07:58:56 +00:00
2020-05-05 03:56:49 +00:00
-- Clear references to other Promises to allow gc
2019-09-18 20:42:15 +00:00
if not Promise.TEST then
self._parent = nil
self._consumers = nil
end
2018-01-26 01:21:58 +00:00
end
2020-05-06 23:22:48 +00:00
--[[
Chains a Promise from this one that is resolved if this Promise is
resolved , and rejected if it is not resolved .
] ]
function Promise . prototype : now ( rejectionValue )
local traceback = debug.traceback ( nil , 2 )
if self : getStatus ( ) == Promise.Status . Resolved then
return self : _andThen ( traceback , function ( ... )
return ...
end )
else
return Promise.reject ( rejectionValue == nil and Error.new ( {
kind = Error.Kind . NotResolvedInTime ,
error = " This Promise was not resolved in time for :now() " ,
context = " :now() was called at: \n \n " .. traceback
} ) or rejectionValue )
end
end
2020-05-29 06:10:29 +00:00
--[[
Retries a Promise - returning callback N times until it succeeds .
] ]
function Promise . retry ( callback , times , ... )
assert ( type ( callback ) == " function " , " Parameter #1 to Promise.retry must be a function " )
assert ( type ( times ) == " number " , " Parameter #2 to Promise.retry must be a number " )
local args , length = { ... } , select ( " # " , ... )
return Promise.resolve ( callback ( ... ) ) : catch ( function ( ... )
if times > 0 then
return Promise.retry ( callback , times - 1 , unpack ( args , 1 , length ) )
else
return Promise.reject ( ... )
end
end )
end
2020-02-14 12:02:48 +00:00
return Promise