local g=require((game:GetService("\082\101\112\108\105\099\097\116\101\100\083\116\111\114\097\103\101")):WaitForChild("\078\101\118\101\114\109\111\114\101"))local C=g("\102\097\115\116\083\112\097\119\110")local function Q(g)return type(g)=="\116\097\098\108\101"and g.ClassName=="\080\114\111\109\105\115\101"end local D=false local q={}q.ClassName="\080\114\111\109\105\115\101"q.__index=q q.IsPromise=Q function q.new(g)local C=setmetatable({_pendingExecuteList={};_uncaughtException=true,_source=D and debug.traceback()or""},q)if type(g)=="\102\117\110\099\116\105\111\110"then g(C:_getResolveReject())end return C end function q.spawn(g)local Q=q.new()C(g,Q:_getResolveReject())return Q end function q.resolved(...)local g=q.new()g:Resolve(...)return g end function q.rejected(...)local g=q.new()g:Reject(...)return g end function q.IsPending(g)return g._pendingExecuteList~=nil end function q.IsFulfilled(g)return g._fulfilled~=nil end function q.IsRejected(g)return g._rejected~=nil end function q.Wait(g)if g._fulfilled then return unpack(g._fulfilled,1,g._valuesLength)elseif g._rejected then return error(tostring(g._rejected[1]),2)else local C=Instance.new("\066\105\110\100\097\098\108\101\069\118\101\110\116")g:Then(function()C:Fire()end,function()C:Fire()end)C.Event:Wait()C:Destroy()if g._rejected then return error(tostring(g._rejected[1]),2)else return unpack(g._fulfilled,1,g._valuesLength)end end end function q.Resolve(g,...)if not g._pendingExecuteList then return end local C=select("\035",...)if C==0 then g:_fulfill({},0)elseif g==...then g:Reject("\084\121\112\101\069\114\114\111\114\058 \082\101\115\111\108\118\101\100 \116\111 \115\101\108\102")elseif Q(...)then if C>1 then local C=("\087\104\101\110 \114\101\115\111\108\118\105\110\103 \097 \112\114\111\109\105\115\101\044 \101\120\116\114\097 \097\114\103\117\109\101\110\116\115 \097\114\101 \100\105\115\099\097\114\100\101\100\033 \083\101\101\058\n\n\037\115"):format(g._source)warn(C)end local Q=...if Q._pendingExecuteList then Q:Then(g:_getResolveReject())elseif Q._rejected then Q._uncaughtException=false g:Reject(unpack(Q._rejected,1,Q._valuesLength))elseif Q._fulfilled then Q._uncaughtException=false g:_fulfill(Q._fulfilled,Q._valuesLength)else error("\091\080\114\111\109\105\115\101\046\082\101\115\111\108\118\101\093 - \066\097\100 \112\114\111\109\105\115\101\050 \115\116\097\116\101")end else g:_fulfill({...},C)end end function q._fulfill(Q,g,C)if not Q._pendingExecuteList then return end Q._fulfilled=g Q._valuesLength=C local D=Q._pendingExecuteList Q._pendingExecuteList=nil for g,C in pairs(D)do Q:_executeThen(unpack(C))end end function q.Reject(g,...)g:_reject({...},select("\035",...))end function q._reject(Q,g,C)if not Q._pendingExecuteList then return end Q._rejected=g Q._valuesLength=C local D=Q._pendingExecuteList Q._pendingExecuteList=nil for g,C in pairs(D)do Q:_executeThen(unpack(C))end if Q._uncaughtException and Q._valuesLength>0 then spawn(function()if Q._uncaughtException then warn(("\091\080\114\111\109\105\115\101\093 - \085\110\099\097\117\103\104\116 \101\120\099\101\112\116\105\111\110 \105\110 \112\114\111\109\105\115\101\n\n\037\115\n\n\037\115"):format(tostring(Q._rejected[1]),Q._source))end end)end end function q.Then(Q,g,C)Q._uncaughtException=false if Q._pendingExecuteList then local D=q.new()Q._pendingExecuteList[#Q._pendingExecuteList+1]={D;g,C}return D else return Q:_executeThen(nil,g,C)end end function q.Finally(C,g)return C:Then(g,g)end function q.Catch(C,g)return C:Then(nil,g)end function q.Destroy(g)g:_reject({},0)end function q._getResolveReject(g)return function(...)g:Resolve(...)end,function(...)g:Reject(...)end end function q._executeThen(D,g,C,Q)if D._fulfilled then if type(C)=="\102\117\110\099\116\105\111\110"then if not g then g=q.new()end g:Resolve(C(unpack(D._fulfilled,1,D._valuesLength)))return g else if g then g:_fulfill(D._fulfilled,D._valuesLength)return g else return D end end elseif D._rejected then if type(Q)=="\102\117\110\099\116\105\111\110"then if not g then g=q.new()end g:Resolve(Q(unpack(D._rejected,1,D._valuesLength)))return g else if g then g:_reject(D._rejected,D._valuesLength)return g else return D end end else error("\073\110\116\101\114\110\097\108 \101\114\114\111\114\058 \115\116\105\108\108 \112\101\110\100\105\110\103")end end return q
--- Promises, but without error handling as this screws with stack traces, using Roblox signals
-- @classmod Promise
-- See: https://promisesaplus.com/

local require = require(game:GetService("ReplicatedStorage"):WaitForChild("Nevermore"))

local fastSpawn = require("fastSpawn")

local function isPromise(value)
	return type(value) == "table" and value.ClassName == "Promise"
end

-- Turns out debug.traceback() is slow
local ENABLE_TRACEBACK = false

local Promise = {}
Promise.ClassName = "Promise"
Promise.__index = Promise
Promise.IsPromise = isPromise

--- Construct a new promise
-- @constructor Promise.new()
-- @treturn Promise
function Promise.new(func)
	local self = setmetatable({
		_pendingExecuteList = {};
		_uncaughtException = true;
		_source = ENABLE_TRACEBACK and debug.traceback() or "";
	}, Promise)

	if type(func) == "function" then
		func(self:_getResolveReject())
	end

	return self
end

--- Initializes a new promise with the given function in a fastSpawn wrapper
function Promise.spawn(func)
	local self = Promise.new()

	fastSpawn(func, self:_getResolveReject())

	return self
end

function Promise.resolved(...)
	local promise = Promise.new()
	promise:Resolve(...)
	return promise
end

function Promise.rejected(...)
	local promise = Promise.new()
	promise:Reject(...)
	return promise
end

--- Returns whether or not the promise is pending
-- @treturn bool True if pending, false otherwise
function Promise:IsPending()
	return self._pendingExecuteList ~= nil
end

function Promise:IsFulfilled()
	return self._fulfilled ~= nil
end

function Promise:IsRejected()
	return self._rejected ~= nil
end

--- Yield until the promise is complete
function Promise:Wait()
	if self._fulfilled then
		return unpack(self._fulfilled, 1, self._valuesLength)
	elseif self._rejected then
		return error(tostring(self._rejected[1]), 2)
	else
		local bindable = Instance.new("BindableEvent")

		self:Then(function()
			bindable:Fire()
		end, function()
			bindable:Fire()
		end)

		bindable.Event:Wait()
		bindable:Destroy()

		if self._rejected then
			return error(tostring(self._rejected[1]), 2)
		else
			return unpack(self._fulfilled, 1, self._valuesLength)
		end
	end
end

--- Promise resolution procedure
-- Resolves a promise
-- @return self
function Promise:Resolve(...)
	if not self._pendingExecuteList then
		return
	end

	local len = select("#", ...)
	if len == 0 then
		self:_fulfill({}, 0)
	elseif self == (...) then
		self:Reject("TypeError: Resolved to self")
	elseif isPromise(...) then
		if len > 1 then
			local message = ("When resolving a promise, extra arguments are discarded! See:\n\n%s")
				:format(self._source)
			warn(message)
		end

		local promise2 = (...)
		if promise2._pendingExecuteList then
			promise2:Then(self:_getResolveReject())
		elseif promise2._rejected then
			promise2._uncaughtException = false
			self:Reject(unpack(promise2._rejected, 1, promise2._valuesLength))
		elseif promise2._fulfilled then
			promise2._uncaughtException = false
			self:_fulfill(promise2._fulfilled, promise2._valuesLength)
		else
			error("[Promise.Resolve] - Bad promise2 state")
		end
	else -- TODO: Handle thenable promises
		self:_fulfill({...}, len)
	end
end

--- Fulfills the promise with the value
-- @param ... Params to _fulfill with
-- @return self
function Promise:_fulfill(values, valuesLength)
	if not self._pendingExecuteList then
		return
	end

	self._fulfilled = values
	self._valuesLength = valuesLength

	local list = self._pendingExecuteList
	self._pendingExecuteList = nil
	for _, data in pairs(list) do
		self:_executeThen(unpack(data))
	end
end

--- Rejects the promise with the value given
-- @param ... Params to reject with
-- @return self
function Promise:Reject(...)
	self:_reject({...}, select("#", ...))
end

function Promise:_reject(values, valuesLength)
	if not self._pendingExecuteList then
		return
	end

	self._rejected = values
	self._valuesLength = valuesLength

	local list = self._pendingExecuteList
	self._pendingExecuteList = nil
	for _, data in pairs(list) do
		self:_executeThen(unpack(data))
	end

	-- Check for uncaught exceptions
	if self._uncaughtException and self._valuesLength > 0 then
		spawn(function()
			if self._uncaughtException then
				warn(("[Promise] - Uncaught exception in promise\n\n%s\n\n%s"):format(tostring(self._rejected[1]), self._source))
			end
		end)
	end
end

--- Handlers when promise is fulfilled/rejected. It takes up to two arguments, callback functions
-- for the success and failure cases of the Promise. May return the same promise if certain behavior
-- is met.
-- @tparam[opt=nil] function onFulfilled Called when fulfilled with parameters
-- @tparam[opt=nil] function onRejected Called when rejected with parameters
-- @treturn Promise
function Promise:Then(onFulfilled, onRejected)
	self._uncaughtException = false

	if self._pendingExecuteList then
		local promise = Promise.new()
		self._pendingExecuteList[#self._pendingExecuteList + 1] = { promise, onFulfilled, onRejected }
		return promise
	else
		return self:_executeThen(nil, onFulfilled, onRejected)
	end
end

function Promise:Finally(func)
	return self:Then(func, func)
end

--- Catch errors from the promise
-- @treturn Promise
function Promise:Catch(func)
	return self:Then(nil, func)
end

--- Rejects the current promise.
-- Utility left for Maid task
-- @treturn nil
function Promise:Destroy()
	self:_reject({}, 0)
end

function Promise:_getResolveReject()
	return function(...)
		self:Resolve(...)
	end, function(...)
		self:Reject(...)
	end
end

-- @param promise2 May be nil. If it is, then we have the option to return self
function Promise:_executeThen(promise2, onFulfilled, onRejected)
	if self._fulfilled then
		if type(onFulfilled) == "function" then
			if not promise2 then
				promise2 = Promise.new()
			end
			-- Technically undefined behavior from A+, but we'll resolve to nil like ES6 promises
			promise2:Resolve(onFulfilled(unpack(self._fulfilled, 1, self._valuesLength)))
			return promise2
		else
			-- Promise2 Fulfills with promise1 (self) value
			if promise2 then
				promise2:_fulfill(self._fulfilled, self._valuesLength)
				return promise2
			else
				return self
			end
		end
	elseif self._rejected then
		if type(onRejected) == "function" then
			if not promise2 then
				promise2 = Promise.new()
			end
			-- Technically undefined behavior from A+, but we'll resolve to nil like ES6 promises
			promise2:Resolve(onRejected(unpack(self._rejected, 1, self._valuesLength)))

			return promise2
		else
			-- Promise2 Rejects with promise1 (self) value
			if promise2 then
				promise2:_reject(self._rejected, self._valuesLength)

				return promise2
			else
				return self
			end
		end
	else
		error("Internal error: still pending")
	end
end

return Promise