Added reject(), bug fixes and rearrange

This commit is contained in:
Samuel Williams 2020-04-08 22:38:12 +01:00
parent f35a9ae643
commit 0b226e0425
6 changed files with 123 additions and 49 deletions

View File

@ -3,7 +3,7 @@ Warning: This is a complex file!
Make sure you are familiar with JavaScript style Promises before attempting to modify this.
]]
local function isPromise( p )
function isPromise( p )
return type( p ) == "table" and p.next ~= nil
end
@ -23,6 +23,12 @@ local function delayReject( prom, ... )
delayPromise( prom, "reject", ... )
end
-- Table for checking yield was from await
local awaitFlag = { awaitFlag = true }
local function isAwaitFlag( v )
return tobool( type( v ) == "table" and v.awaitFlag )
end
--[[
How this works:
@ -64,13 +70,21 @@ promiseReturn = function( f, state, coInput, rootPromiseInput, debugInfoInput )
local success = table.remove( ret, 1 )
if success then
-- ret[1] will be a promise whenever await is called
if isPromise( ret[1] ) then
if isAwaitFlag( ret[1] ) then
-- If it's a promise, set that promise's reject and resolve to put it on the promise stack
local awaitPromise = ret[2]
local resolve = promiseReturn( f, true, co, rootPromise, debugInfo )
local reject = promiseReturn( f, false, co, rootPromise, debugInfo )
ret[1]:next( resolve, reject )
awaitPromise:next( resolve, reject )
elseif isPromise( ret[1] ) then
-- Return result was a promise, make it's reject and resolve forward to rootPromise
ret[1]:next( function( ... )
rootPromise:resolve( ... )
end, function( ... )
rootPromise:reject( ... )
end )
else
-- If it's not a promise, this means the coroutine finished or the promise stack is empty ( from errors )
delayResolve( rootPromise, unpack( ret ) )
@ -78,8 +92,12 @@ promiseReturn = function( f, state, coInput, rootPromiseInput, debugInfoInput )
else
-- There was a normal error in the coroutine, just reject with the error and location
local err = ret[1]
local locationText = "\nContaining async function defined in " .. debugInfo.short_src .. " at line " .. debugInfo.linedefined .. "\n"
delayReject( rootPromise, err, locationText )
if type( err ) == "table" and err.reject then
delayReject( rootPromise, unpack( err.reject ) )
else
local locationText = "\nContaining async function defined in " .. debugInfo.short_src .. " at line " .. debugInfo.linedefined .. "\n"
delayReject( rootPromise, err, debug.traceback( co ), locationText )
end
end
-- We only return the rootPromise at root, else it will cause every "next" call above to be waiting on the rootPromise
@ -110,20 +128,25 @@ AwaitTypes = {
function await( p, awaitType, arg )
assert( coroutine.running(), "Cannot use await outside of async function" )
if not isPromise( p ) then
-- If not a promise, it doesn't need to be waited for
return p
end
awaitType = awaitType or AwaitTypes.RETURN
local data = { coroutine.yield( p ) }
local data = { coroutine.yield( awaitFlag, p ) }
local success = table.remove( data, 1 )
if awaitType == AwaitTypes.RETURN then
return success, unpack( data )
elseif awaitType == AwaitTypes.PROPAGATE then
if not success then
error( data[1] )
reject( unpack( data ) )
end
return unpack( data )
elseif awaitType == AwaitTypes.MESSAGE_OVERRIDE then
error( arg )
reject( arg )
elseif awaitType == AwaitTypes.HANDLER then
if not success then
arg( unpack( data ) )
@ -133,6 +156,30 @@ function await( p, awaitType, arg )
end
end
local function stringifyArgs( ... )
local out = {}
for k, v in pairs{ ... } do
if istable( v ) then
out[k] = table.ToString( v )
else
out[k] = tostring( v )
end
end
return table.concat( out, ", " )
end
function reject( ... )
if inAsync() then
error( { reject = { ... } } )
else
error( stringifyArgs( ... ) )
end
end
function inAsync()
return tobool( coroutine.running() )
end
--[[
Old simpler await definition, in case we decide to change back.

View File

@ -7,4 +7,4 @@ NP = networkPromise -- shorthand name, networkPromise will get a bit cumbersome
include( "network_promises/http.lua" )
include( "network_promises/net.lua" )
include( "network_promises/async.lua" )
include( "network_promises/xdcall.lua" )
include( "network_promises/util.lua" )

View File

@ -86,6 +86,7 @@ function NP.http.requestIndef( method, url, overrides )
table.Merge( struct, overrides )
struct.headers.Authorization = overrides.authToken
HTTP( struct )
return prom

View File

@ -25,20 +25,19 @@ local function finish( deferred, state )
end
end
if state == REJECTED and #deferred.queue == 0 then
timer.Simple( 0, function()
local errText = ""
for k, v in ipairs( deferred.value ) do
if type( v ) == "table" then
errText = errText .. table.ToString( v ) .. "\n"
else
errText = errText .. tostring( v ) .. "\n"
end
local errText = ""
for k, v in ipairs( deferred.value ) do
if type( v ) == "table" then
errText = errText .. table.ToString( v ) .. "\n"
else
errText = errText .. tostring( v ) .. "\n"
end
if #errText > 500 then
errText = errText:sub( 1, 500 ) .. "..."
end
error( "Uncaught rejection or exception in promise:\n" .. errText .. "IGNORE FOLLOWING 4 LINES" )
end )
end
if #errText > 1000 then
errText = errText:sub( 1, 1000 ) .. "...\n"
end
ErrorNoHalt( "Uncaught rejection or exception in promise:\n" .. errText )
print("hi")
end
deferred.state = state
end
@ -58,7 +57,7 @@ local function promise( deferred, next, success, failure, nonpromisecb )
failure()
end )
if not ok and not called then
deferred.value = { err, stack }
deferred.value = handleError( deferred, { err, stack } )
failure()
end
else
@ -66,6 +65,19 @@ local function promise( deferred, next, success, failure, nonpromisecb )
end
end
local function handleError( deferred, values )
print("handle this shit")
local first = values[1]
if type( first ) == "table" and first.reject then
return unpack( first.reject )
end
table.insert( values, "\nContaining next defined in " .. deferred.successInfo.short_src ..
" at line " .. deferred.successInfo.linedefined .. "\n" )
return values
end
local function fire( deferred )
local next
if type( deferred.value[1] ) == "table" then
@ -79,32 +91,30 @@ local function fire( deferred )
fire( deferred )
end, function()
local ok
local v
local value
if deferred.state == RESOLVING and isfunction( deferred.success ) then
local ret = { xdcall( deferred.success, unpack( deferred.value ) ) }
ok = table.remove( ret, 1 )
v = ret
value = ret
if not ok then
table.insert( ret, "\nContaining next defined in " .. deferred.successInfo.short_src ..
" at line " .. deferred.successInfo.linedefined .. "\n" )
value = handleError( deferred, value )
end
elseif deferred.state == REJECTING and isfunction( deferred.failure ) then
local ret = { xdcall( deferred.failure, unpack( deferred.value ) ) }
ok = table.remove( ret, 1 )
v = ret
value = ret
if ok then
deferred.state = RESOLVING
else
table.insert( ret, "\nContaining next defined in " .. deferred.failureInfo.short_src ..
" at line " .. deferred.failureInfo.linedefined .. "\n" )
value = handleError( deferred, value )
end
end
if ok ~= nil then
if ok then
deferred.value = v
deferred.value = value
else
deferred.value = v
deferred.value = value
return finish( deferred )
end
end

View File

@ -0,0 +1,32 @@
-- Same as pcall, but returns error message and traceback on error
function xdcall( func, ... )
local traceback
local data = { xpcall( func, function( err )
traceback = debug.traceback( tostring( err ) )
return err
end, ... ) }
local success = table.remove( data, 1 )
if success then
return true, unpack( data )
end
if type( data[1] ) == "table" and data[1].reject then
return false, data[1], traceback
end
return false, traceback
end
-- Hatling error with the little popup, but no traceback
function errorNoTrace( err )
local mt = {
__tostring = function( self )
return err
end
}
local errObj = setmetatable( {}, mt )
error( errObj )
end

View File

@ -1,16 +0,0 @@
-- Same as pcall, but returns error message and traceback on error
function xdcall( func, ... )
local traceback
local data = { xpcall( func, function( err )
traceback = debug.traceback()
return err
end, ... ) }
local success = table.remove( data, 1 )
if success then
return true, unpack( data )
end
return false, data[1], traceback
end