2024-06-06 06:05:04 +02:00
--- Javascript Promises, implemented in Lua
-- In other words, a wrapper to manage asynchronous operations.
-- Due to limitations of Lua, while this Promise API is similar it isn't exactly the same as in JS.
--
-- Also, .then_() does not return a thenable value but the SAME ORIGINAL promise, as we stack up all functions and then execute them in order once you call .run(). This has the subtle implication that Promise.state is not set to "fulfilled" until ALL functions in the chain have been called.
--
-- Additionally, every .then_(function(...) end) MAY return a promise themselves if they wish. These promises WILL be automatically executed when you call .run() on the PARENT promise, as they are considered required for the parent Promise function chain to run to completion.
-- @class worldeditadditions_core.Promise
local Promise = { }
Promise.__index = Promise
Promise.__name = " Promise " -- A hack to allow identification in wea.inspect
--- Creates a new Promise instance.
-- @param fn <function>: The function to wrap into a promise.
Promise.new = function ( fn )
-- resolve must be a function
if type ( fn ) ~= " function " then
error ( " Error (Promise.new): First argument (fn) must be a function " )
end
local result = {
state = " pending " ,
2024-06-06 17:33:17 +02:00
force_reject = false ,
2024-06-06 06:05:04 +02:00
fn = fn
}
setmetatable ( result , Promise )
return result
end
--[[
*************************
Instance methods
*************************
--]]
2024-06-06 17:33:17 +02:00
--- A dummy function
local f = function ( val ) end
2024-06-06 06:05:04 +02:00
--- Then function for promises
-- @param onFulfilled <function>: The function to call if the promise is fulfilled
-- @param onRejected <function>: The function to call if the promise is rejected
2024-06-06 17:33:17 +02:00
-- @return A promise object containing a table of results
2024-06-06 06:05:04 +02:00
Promise.then_ = function ( self , onFulfilled , onRejected )
2024-06-06 17:33:17 +02:00
-- onFulfilled must be a function or nil
if onFulfilled == nil then onFulfilled = f
elseif type ( onFulfilled ) ~= " function " then
2024-06-06 06:05:04 +02:00
error ( " Error (Promise.then_): First argument (onFulfilled) must be a function or nil " )
end
-- onRejected must be a function or nil
2024-06-06 17:33:17 +02:00
if onRejected == nil then onRejected = f
elseif type ( onRejected ) ~= " function " then
2024-06-06 06:05:04 +02:00
error ( " Error (Promise.then_): Second argument (onRejected) must be a function or nil " )
end
-- If self.state is not "pending" then error
if self.state ~= " pending " then
2024-06-06 22:12:21 +02:00
return Promise.reject ( " Error (Promise.then_): Promise is already " .. self.state )
2024-06-06 06:05:04 +02:00
end
2024-06-06 17:33:17 +02:00
-- Make locals to collect the results of self.fn
local result , force_reject = { nil } , self.force_reject
-- Local resolve and reject functions
local _resolve = function ( value ) result [ 1 ] = value end
local _reject = function ( value )
result [ 1 ] = value
force_reject = true
2024-06-06 06:05:04 +02:00
end
2024-06-06 17:33:17 +02:00
-- Call self.fn
local success , err = pcall ( self.fn , _resolve , _reject )
-- Return a new promise with the results
if success and not force_reject then
onFulfilled ( result [ 1 ] )
2024-06-06 22:12:21 +02:00
self.state = " fulfilled "
2024-06-06 17:33:17 +02:00
return Promise.resolve ( result [ 1 ] )
else
onRejected ( result [ 1 ] )
2024-06-06 22:12:21 +02:00
self.state = " rejected "
2024-06-06 17:33:17 +02:00
return Promise.reject ( success and result [ 1 ] or err )
end
2024-06-06 06:05:04 +02:00
end
--- Catch function for promises
-- @param onRejected <function>: The function to call if the promise is rejected
-- @return A promise object
Promise.catch = function ( self , onRejected )
-- onRejected must be a function
if type ( onRejected ) ~= " function " then
error ( " Error (Promise.catch): First argument (onRejected) must be a function " )
end
return Promise.then_ ( self , nil , onRejected )
end
--- Finally function for promises
-- @param onFinally <function>: The function to call if the promise becomes settled
-- @return A promise object
Promise.finally = function ( self , onFinally )
onFinally ( )
return Promise.new ( self.fn )
end
--[[
*************************
Static methods
*************************
--]]
--- Resolve function for promises
-- @param value <any>: The value to resolve the promise with
-- @return A promise object
Promise.resolve = function ( value )
return Promise.new ( function ( resolve , reject )
resolve ( value )
end )
end
--- Reject function for promises
-- @param value <any>: The value to reject the promise with
-- @return A promise object
Promise.reject = function ( value )
2024-06-06 17:33:17 +02:00
local promise = Promise.new ( function ( resolve , reject )
2024-06-06 06:05:04 +02:00
reject ( value )
end )
2024-06-06 17:33:17 +02:00
promise.force_reject = true
return promise
end
-- TODO: Implement static methods (all, any, race etc.)
return Promise
--- TESTS
--[[
Promise = require " promise_tech "
tmp = Promise.resolve ( 5 )
2024-06-06 22:12:21 +02:00
tmp : then_ ( print , nil ) : then_ ( print , nil ) : then_ ( print , nil )
2024-06-06 17:33:17 +02:00
tmp = Promise.reject ( 7 )
2024-06-06 22:12:21 +02:00
tmp : then_ ( nil , print ) : then_ ( nil , print ) : then_ ( nil , print )
2024-06-06 17:33:17 +02:00
--- BIG TESTS
function test ( )
return Promise.new ( function ( resolve , reject )
local value = math.random ( ) -- imagine this was async
if value > 0.5 then
reject ( value )
else
resolve ( value )
end
end )
end
function do_if_passes ( val )
print ( " I passed! " )
print ( " My value was " .. tostring ( val ) )
end
function do_if_fails ( err )
print ( " I failed! " )
print ( " My error was " .. tostring ( err ) )
2024-06-06 06:05:04 +02:00
end
2024-06-06 17:33:17 +02:00
test ( ) : then_ ( do_if_passes , do_if_fails )
2024-06-06 22:12:21 +02:00
Vx2 = 0
test ( ) : then_ ( function ( value ) Vx2 = value end , function ( value ) print ( " caught rejection, value " , value ) end ) :
then_ ( function ( value ) print ( " Sqrt is " , math.sqrt ( value ) ) end )
if Vx2 ~= 0 then print ( " Vx2 " , Vx2 ) end
2024-06-06 17:33:17 +02:00
] ]