Added xdcall, net.http.request, improved promise errors

This commit is contained in:
Samuel Williams 2020-02-24 23:24:36 +00:00
parent 7f2c437c68
commit 784270082e
5 changed files with 104 additions and 22 deletions

View File

@ -7,3 +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" )

View File

@ -1,6 +1,7 @@
NP.http = {}
-- Returns a promise that resolves after t seconds
-- fail : Should the promise reject after timeout
function NP.timeout( t, fail )
method = fail and "reject" or "resolve"
local d = promise.new()
@ -10,20 +11,24 @@ function NP.timeout( t, fail )
return d
end
local function responseSuccess( d, body, status, headers )
-- Check body is valid Json, if not, reject
local data = util.JSONToTable( body )
local method = "reject"
if math.floor( status / 100 ) == 2 and data then
method = "resolve"
end
d[method]( d, data or "Invalid json response", status, headers )
end
-- http.post as a promise, resolves whenever http finishes, or never if it doesn't (Looking at you, ISteamHTTP)
-- url : post url
-- data : post args as table
-- resolves to function( statusCode, data, headers )
-- resolves to function( data, statusCode, headers )
function NP.http.postIndef( url, data )
local d = promise.new() -- promise itself
http.Post( url, data, function( body, len, headers, status )
-- Check body is valid Json, if not, reject
local data = util.JSONToTable( body )
local method = "reject"
if math.floor(status / 100) == 2 and data then
method = "resolve"
end
d[method]( d, data or "Invalid json response", status, headers )
responseSuccess( d, body, status, headers )
end, function( err )
d:reject( err, -1 )
end )
@ -34,25 +39,45 @@ end
function NP.http.fetchIndef( url )
local d = promise.new()
http.Fetch( url, function( body, len, headers, status )
-- Check body is valid Json, if not, reject
local data = util.JSONToTable( body )
local method = "reject"
if math.floor(status / 100) == 2 then
method = "resolve"
end
d[method]( d, data or "Invalid json response", status, headers )
responseSuccess( d, body, status, headers )
end, function( err )
d:reject( err, -1 )
end )
return d
end
-- HTTP as a promise, resolves whenever http finishes, or never if it doesn't (Looking at you, ISteamHTTP)
-- method : GET, PUT, etc.
-- endPoint : Appended to settings.apiRoot
-- params : params for GET, POST, HEAD
-- settings : { apiRoot = "myRoot", apiKey = "myKey", timeout = 5 } - timeout only functional in NP.http.request
-- resolves to function( data, statusCode, headers )
function NP.http.requestIndef( method, endPoint, params, settings )
method = method or "GET"
local d = promise.new()
local url = settings.apiRoot .. endPoint
local struct = HTTPRequest({
failed = function( err )
d:reject( err, -1 )
end,
success = function( status, body, headers )
responseSuccess( d, body, status, headers )
end,
method = method,
url = url,
parameters = params,
type = "application/json",
Token = settings.apiKey
})
end
-- Post but with enforced timeout
-- This promise is guaranteed to resolve/reject eventually
-- url : post url
-- data : post args as table
-- timeout : optional timeout in seconds (def 5)
-- resolves to function( statusCode, data, headers )
-- resolves to function( data, statusCode, headers )
function NP.http.post( url, data, timeout )
timeout = timeout or 5
return promise.first{
@ -69,3 +94,12 @@ function NP.http.fetch( url, timeout )
NP.timeout( timeout, true )
}
end
-- Same as above but for request
function NP.http.request( method, endPoint, params, settings )
local timeout = settings.timeout or 5
return promise.first{
NP.http.fetchIndef( url ),
NP.timeout( timeout, true )
}
end

View File

@ -25,7 +25,10 @@ local function finish(deferred, state)
end
end
if state == REJECTED and #deferred.queue == 0 then
error("Uncaught rejection or exception in promise:\n" .. table.concat(deferred.value, "\t"))
timer.Simple(0, function()
error("Uncaught rejection or exception in promise:\n" .. table.concat(deferred.value, "\n") .. "IGNORE FOLLOWING 4 LINES")
end )
end
deferred.state = state
end
@ -41,7 +44,7 @@ end
local function promise(deferred, next, success, failure, nonpromisecb)
if type(deferred) == 'table' and type(deferred.value[1]) == 'table' and isfunction(next) then
local called = false
local ok, err = pcall(next, deferred.value[1], function( ... )
local ok, err, stack = xdcall(next, deferred.value[1], function( ... )
if called then return end
called = true
deferred.value = { ... }
@ -53,7 +56,7 @@ local function promise(deferred, next, success, failure, nonpromisecb)
failure()
end)
if not ok and not called then
deferred.value = { err }
deferred.value = { err, stack }
failure()
end
else
@ -76,14 +79,14 @@ local function fire(deferred)
local ok
local v
if deferred.state == RESOLVING and isfunction(deferred.success) then
local ret = { pcall(deferred.success, unpack(deferred.value)) }
local ret = { xdcall(deferred.success, unpack(deferred.value)) }
ok = table.remove(ret, 1)
v = ret
if not ok then
table.insert(ret, "\nContaining next defined in " .. deferred.successInfo.short_src .. " at line " .. deferred.successInfo.linedefined .. "\n")
end
elseif deferred.state == REJECTING and isfunction(deferred.failure) then
local ret = { pcall(deferred.failure, unpack(deferred.value)) }
local ret = { xdcall(deferred.failure, unpack(deferred.value)) }
ok = table.remove(ret, 1)
v = ret
if ok then

View File

@ -54,7 +54,7 @@ function NP.net.receive( name, func )
net.WriteTable( args )
net.Send( ply )
end
if ret[1] and ret[1].next then
if ret[1] and type(ret[1]) == "table" and ret[1].next then
ret[1]:next( function( ... )
finish( true, { ... } )
end,

View File

@ -0,0 +1,44 @@
networkPromise.oldYield = networkPromise.oldYield or coroutine.yield
local oldYield = networkPromise.oldYield
-- like pcall but returns a real traceback back as well
-- xpcall allows you to get a traceback, but its not very good. Getting the trace from an errored coroutine is more accurate
-- Unique object that nothing else could return
local trueYield = {}
local function capturedYield( ... )
return oldYield( trueYield, ... )
end
-- takes func, args
-- if success, returns true, retArgs
-- if not , returns false, error, stack
function xdcall( func, ... )
local co = coroutine.create( func )
local args = { ... }
while true do
coroutine.yield = capturedYield
local data = { coroutine.resume( co, unpack(args) ) }
coroutine.yield = oldYield
--p(data)
local success = table.remove( data, 1 )
if success then
if data[1] == trueYield then
-- They called yield internally
table.remove( data, 1 )
args = { oldYield( unpack( data ) ) }
else
-- successfully finished running
return true, unpack( data )
end
else
-- thats an error, dawg
local err = data[1]
return false, err, debug.traceback( co )
end
end
end