mirror of
https://github.com/sbrl/Minetest-WorldEditAdditions.git
synced 2024-12-04 21:13:50 +01:00
promise.lua: initial UNTESTED implementation of JS promises in Lua o/
Got some caveats and subtle behavioural differences because Lua is dumb, but overall pretty happy with it! This commit contains the most complicated recursive function I have ever written to date. It's late and I have absolutely *no* intention of testing this tonight :P
This commit is contained in:
parent
e2167c4ba3
commit
1300e9cc1d
155
promise.lua
Normal file
155
promise.lua
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
--- 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.
|
||||||
|
--
|
||||||
|
-- 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.
|
||||||
|
function Promise.new(fn)
|
||||||
|
local result = {
|
||||||
|
state = "pending",
|
||||||
|
fns = { { 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
|
||||||
|
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)
|
||||||
|
---
|
||||||
|
-- 0: Termination condition
|
||||||
|
---
|
||||||
|
if #promise.fns == 0 then
|
||||||
|
promise.state = "fulfilled"
|
||||||
|
origin_resolve(table.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)
|
||||||
|
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
|
||||||
|
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
|
||||||
|
end, function(...) -- REJECT
|
||||||
|
promise.state = "rejected"
|
||||||
|
if type(next.catch_) == "function" then
|
||||||
|
next.catch_(table.unpack(arg))
|
||||||
|
else
|
||||||
|
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)) }
|
||||||
|
|
||||||
|
---
|
||||||
|
-- 3: Handle the aftermath
|
||||||
|
---
|
||||||
|
if #results == 1 and getmetatable(results[1]) == Promise then
|
||||||
|
-- It's a promise!
|
||||||
|
-- Calling do_run does NOT cause an infinite loop, because fns[1] is the INNER function that expects to be passed resolve, reject
|
||||||
|
do_run(
|
||||||
|
results[1], -- The child promise
|
||||||
|
{},
|
||||||
|
1,
|
||||||
|
function(...)
|
||||||
|
-- Once this entire subchain is done, do the next function in this chain
|
||||||
|
do_next_fn(arg)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
else
|
||||||
|
-- Not a promise, carry on
|
||||||
|
do_next_fn(results)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
-- 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()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user