promise/sbrl: bugfix

it should work in most situations now.
This commit is contained in:
Starbeamrainbowlabs 2024-06-07 00:08:12 +01:00
parent 3ccea536c8
commit f654bde966
Signed by: sbrl
GPG key ID: 1BE5172E637709C2

View file

@ -1,3 +1,5 @@
local inspect = require("worldeditadditions_core.utils.inspect")
--- Javascript Promises, implemented in Lua --- Javascript Promises, implemented in Lua
-- In other words, a wrapper to manage asynchronous operations. -- 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. Most notably is that there is a .run() function you must call AFTER you call .then_() as many times as you need. -- Due to limitations of Lua, while this Promise API is similar it isn't exactly the same as in JS. Most notably is that there is a .run() function you must call AFTER you call .then_() as many times as you need.
@ -15,50 +17,30 @@ Promise.__name = "Promise" -- A hack to allow identification in wea.inspect
function Promise.new(fn) function Promise.new(fn)
local result = { local result = {
state = "pending", state = "pending",
fns = { { then_ = fn } } fns = { { fn_then_ = fn } }
} }
setmetatable(result, Promise) setmetatable(result, Promise)
return result return result
end end
-- NOTE: fn_catch support is NOT FULLY IMPLEMENTED yet!
-- Example: It doesn't work when calling non-async .then()s! ------------------------------------------------------------------------
function Promise.then_(self, fn_then, fn_catch)
local step_item = { local do_unpack = unpack
then_ = fn_then, if not do_unpack then do_unpack = table.unpack end
} if not do_unpack then
if type(fn_catch) == "function" then error("Error: Failed to find unpack implementation")
step_item.catch_ = fn_catch
end end
table.insert(self.fns, step_item)
return self
end
--- Executes the function this Promise is wrapping all associated chained functions in sequence.
-- This is a separate method for portability, since Lua does not implement setTimeout which is required to ensure that if an non-async function is wrapped the parent function still has time to call .then_() before it finishes and the associated .then_()ed functions are attached correctly.
--
-- WARNING: If you call .then_() AFTER calling .run(), your .then_()ed function may not be called correctly!
-- @returns Promise The current promise, for daisy chaining purposes.
function Promise.run(self)
-- TODO update self.state here
do_run(promise, {}, 1, function(...)
end)
return self
end
local function do_run(promise, args, depth, origin_resolve) local function do_run(promise, args, depth, origin_resolve)
-- print("DO_RUN promise", inspect(promise), "args", inspect(args), "depth", depth, "origin_resolve", origin_resolve)
--- ---
-- 0: Termination condition -- 0: Termination condition
--- ---
if #promise.fns == 0 then if #promise.fns == 0 then
promise.state = "fulfilled" promise.state = "fulfilled"
origin_resolve(table.unpack(args)) origin_resolve(do_unpack(args))
return return
end end
@ -68,8 +50,8 @@ local function do_run(promise, args, depth, origin_resolve)
if args == nil then args = {} end if args == nil then args = {} end
local next = table.remove(promise.fns, 1) local next = table.remove(promise.fns, 1)
local do_next_fn = function(args) local do_next_fn = function(args_to_pass)
do_run(promise, args, depth + 1) do_run(promise, args_to_pass, depth + 1, origin_resolve)
end end
@ -82,24 +64,25 @@ local function do_run(promise, args, depth, origin_resolve)
if depth == 1 then if depth == 1 then
-- This function is the inner one of a promise already. -- This function is the inner one of a promise already.
-- It has already recieved arguments, so it expected resolve/reject instead -- It has already recieved arguments, so it expected resolve/reject instead
next.then_(function(...) -- RESOLVE next.fn_then_(function(...) -- RESOLVE
if getmetatable(arg[1]) == Promise then if getmetatable(arg[1]) == Promise then
promise.state = "rejected" promise.state = "rejected"
error("Error: Returning nested Promises is not currently supported.") error("Error: Returning nested Promises is not currently supported.")
end end
do_next_fn(args) -- Execute the next one in the sequence do_next_fn(arg) -- Execute the next one in the sequence
end, function(...) -- REJECT end, function(...) -- REJECT
promise.state = "rejected" promise.state = "rejected"
if type(next.catch_) == "function" then if type(next.fn_catch_) == "function" then
next.catch_(table.unpack(arg)) next.fn_catch_(do_unpack(arg))
else else
error("Error: Promise rejected but no catch function present. TODO make this error message more useful and informative.") error(
"Error: Promise rejected but no catch function present. TODO make this error message more useful and informative.")
-- If there's no catch registered, throw an error -- If there's no catch registered, throw an error
end end
end) end)
else else
results = { next.then_(table.unpack(args)) } results = { next.fn_then_(do_unpack(args)) }
--- ---
-- 3: Handle the aftermath -- 3: Handle the aftermath
@ -121,35 +104,67 @@ local function do_run(promise, args, depth, origin_resolve)
do_next_fn(results) do_next_fn(results)
end end
end end
end
------------------------------------------------------------------------
-- NOTE: fn_catch support is NOT FULLY IMPLEMENTED yet!
-- Example: It doesn't work when calling non-async .then()s!
function Promise.then_(self, fn_then, fn_catch)
local step_item = {
fn_then_ = fn_then,
}
if type(fn_catch) == "function" then
step_item.fn_catch_ = fn_catch
end
table.insert(self.fns, step_item)
return self
end
--- Executes the function this Promise is wrapping all associated chained functions in sequence.
-- This is a separate method for portability, since Lua does not implement setTimeout which is required to ensure that if an non-async function is wrapped the parent function still has time to call .then_() before it finishes and the associated .then_()ed functions are attached correctly.
--
-- WARNING: If you call .then_() AFTER calling .run(), your .then_()ed function may not be called correctly!
-- @returns Promise The current promise, for daisy chaining purposes.
function Promise.run(self)
-- TODO update self.state here
do_run(self, {}, 1, function(...)
end)
return self
end end
return Promise
-- return Promise
-- TEST example code, TODO test this -- TEST example code, TODO test this
-- function test() function test()
-- return Promise.new(function(resolve, reject) return Promise.new(function(resolve, reject)
-- -- TODO do something asyncy here -- TODO do something asyncy here
-- print("DEBUG running test function") print("DEBUG running test function")
-- resolve(4) resolve(4)
-- end) end)
-- end end
-- test() test()
-- .then_(function(val) :then_(function(val)
-- print("DEBUG then #1 VAL", val) print("DEBUG then #1 VAL", val)
-- return val * 2 return val * 2
-- end) end)
-- .then_(function(val) :then_(function(val)
-- print("DEBUG then #2 VAL", val) print("DEBUG then #2 VAL", val)
-- return math.sqrt(val) return math.sqrt(val)
-- end) end)
-- .then_(function(val) :then_(function(val)
-- print("DEBUG then #3 VAL", val) print("DEBUG then #3 VAL", val)
-- end) end)
-- .run() :run()