diff --git a/promise.lua b/promise.lua index f1a94b9..3c9a5eb 100644 --- a/promise.lua +++ b/promise.lua @@ -1,3 +1,5 @@ +local inspect = require("worldeditadditions_core.utils.inspect") + --- 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. Most notably is that there is a .run() function you must call AFTER you call .then_() as many times as you need. @@ -15,92 +17,73 @@ Promise.__name = "Promise" -- A hack to allow identification in wea.inspect function Promise.new(fn) local result = { state = "pending", - fns = { { then_ = fn } } + fns = { { fn_then_ = fn } } } setmetatable(result, Promise) return result 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 = { - then_ = fn_then, - } - if type(fn_catch) == "function" then - step_item.catch_ = fn_catch - end - - table.insert(self.fns, step_item) - - return self + +------------------------------------------------------------------------ + +local do_unpack = unpack +if not do_unpack then do_unpack = table.unpack end +if not do_unpack then + error("Error: Failed to find unpack implementation") 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) + -- print("DO_RUN promise", inspect(promise), "args", inspect(args), "depth", depth, "origin_resolve", origin_resolve) --- -- 0: Termination condition --- if #promise.fns == 0 then promise.state = "fulfilled" - origin_resolve(table.unpack(args)) + origin_resolve(do_unpack(args)) return end - + --- -- 1: Sort out inputs --- if args == nil then args = {} end local next = table.remove(promise.fns, 1) - - local do_next_fn = function(args) - do_run(promise, args, depth + 1) + + local do_next_fn = function(args_to_pass) + do_run(promise, args_to_pass, depth + 1, origin_resolve) end - - + + --- -- 2: Run teh function! --- - + -- (if it's the 1st one in the sequence we treat it specially) local results if depth == 1 then -- This function is the inner one of a promise already. -- 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 promise.state = "rejected" error("Error: Returning nested Promises is not currently supported.") 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 promise.state = "rejected" - if type(next.catch_) == "function" then - next.catch_(table.unpack(arg)) + if type(next.fn_catch_) == "function" then + next.fn_catch_(do_unpack(arg)) 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 end end) else - results = { next.then_(table.unpack(args)) } - + results = { next.fn_then_(do_unpack(args)) } + --- -- 3: Handle the aftermath --- @@ -121,35 +104,67 @@ local function do_run(promise, args, depth, origin_resolve) do_next_fn(results) 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 -return Promise + + +-- return Promise -- TEST example code, TODO test this --- function test() --- return Promise.new(function(resolve, reject) --- -- TODO do something asyncy here --- print("DEBUG running test function") --- resolve(4) --- end) --- end +function test() + return Promise.new(function(resolve, reject) + -- TODO do something asyncy here + print("DEBUG running test function") + resolve(4) + end) +end --- test() --- .then_(function(val) --- print("DEBUG then #1 VAL", val) --- return val * 2 --- end) --- .then_(function(val) --- print("DEBUG then #2 VAL", val) --- return math.sqrt(val) --- end) --- .then_(function(val) --- print("DEBUG then #3 VAL", val) --- end) --- .run() +test() + :then_(function(val) + print("DEBUG then #1 VAL", val) + return val * 2 + end) + :then_(function(val) + print("DEBUG then #2 VAL", val) + return math.sqrt(val) + end) + :then_(function(val) + print("DEBUG then #3 VAL", val) + end) + :run()