Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- I was testing a script in Roblox Studio and the following errors appeared. I clicked on the errors and it brought me to a few scripts within Roblox Studio that you cannot normally see. The script I was running was not made with the purpose of doing this, something simply went wrong within Roblox Studio, and this happened. I would have put this on the Roblox DevForum, but I can't make posts there due to my rank being nonexistent. It simply didn't give me a rank. Not even visitor. I have no idea if it is against any rules to put source code here, and if it is, there is nothing I can do about it since I'm not going to make an account, meaning that I can't delete this.
- error messages printed in output:
- CorePackages.Packages._Index.roblox_rodux.rodux.thunkMiddleware:15
- 14:27:40.327 Unable to get game name for Avatar Editor Prompts - Client - GetGameName:29
- 14:27:40.327 Unable to get game name for Gamepad Menu - Client - GetGameName:29
- 14:27:40.332 Unhandled Promise rejection:
- HTTP 500 (Internal Server Error)
- CoreGui.RobloxGui.CoreScripts/ScreenshotHud:328 function fetchExperienceName
- CoreGui.RobloxGui.CoreScripts/ScreenshotHud:661
- - Client - Promise:1299
- 14:27:40.332 Unhandled Promise rejection:
- nil
- CoreGui.RobloxGui.Modules.TopBar.Thunks.GetGameName:23
- CorePackages.Packages._Index.roblox_rodux.rodux.thunkMiddleware:15
- CorePackages.Packages._Index.roblox_rodux.rodux.thunkMiddleware:14
- CorePackages.Packages._Index.roblox_rodux.rodux.Store:98
- CoreGui.RobloxGui.Modules.TopBar:88 function new
- CoreGui.RobloxGui.Modules.TopBar:139
- - Client - Promise:1299
- 14:27:40.332 Unhandled Promise rejection:
- nil
- CoreGui.RobloxGui.Modules.AvatarEditorPrompts.Thunks.GetGameName:23
- CorePackages.Packages._Index.roblox_rodux.rodux.thunkMiddleware:15
- CorePackages.Packages._Index.roblox_rodux.rodux.thunkMiddleware:14
- CorePackages.Packages._Index.roblox_rodux.rodux.Store:98
- CoreGui.RobloxGui.Modules.AvatarEditorPrompts:43 function new
- CoreGui.RobloxGui.Modules.AvatarEditorPrompts:81
- - Client - Promise:1299
- script name:
- CoreGui.RobloxGui.Modules.AvatarEditorPrompts.Thunks.GetGameName
- code:
- local CoreGui = game:GetService("CoreGui")
- local CorePackages = game:GetService("CorePackages")
- local HttpRbxApiService = game:GetService("HttpRbxApiService")
- local RobloxGui = CoreGui:WaitForChild("RobloxGui")
- local Promise = require(CorePackages.Promise)
- local httpRequest = require(CorePackages.AppTempCommon.Temp.httpRequest)
- local httpImpl = httpRequest(HttpRbxApiService)
- local Thunks = script.Parent
- local AvatarEditorPrompts = Thunks.Parent
- local GameNameFetched = require(AvatarEditorPrompts.Actions.GameNameFetched)
- local GetGameNameAndDescription = require(RobloxGui.Modules.Common.GetGameNameAndDescription)
- return function(store)
- if game.GameId == 0 then
- return Promise.resolve()
- end
- return GetGameNameAndDescription(httpImpl, game.GameId):andThen(function(result)
- store:dispatch(GameNameFetched(result.Name))
- return result.Name
- end,
- function()
- warn("Unable to get game name for Avatar Editor Prompts")
- return Promise.reject()
- end)
- end
- script name:
- CoreGui.RobloxGui.Modules.TopBar.Thunks.GetGameName
- code:
- local CoreGui = game:GetService("CoreGui")
- local CorePackages = game:GetService("CorePackages")
- local HttpRbxApiService = game:GetService("HttpRbxApiService")
- local RobloxGui = CoreGui:WaitForChild("RobloxGui")
- local Promise = require(CorePackages.Promise)
- local httpRequest = require(CorePackages.AppTempCommon.Temp.httpRequest)
- local httpImpl = httpRequest(HttpRbxApiService)
- local Thunks = script.Parent
- local TopBar = Thunks.Parent
- local SetGameName = require(TopBar.Actions.SetGameName)
- local GetGameNameAndDescription = require(RobloxGui.Modules.Common.GetGameNameAndDescription)
- return function(store)
- if game.GameId == 0 then
- return Promise.resolve()
- end
- return GetGameNameAndDescription(httpImpl, game.GameId):andThen(function(result)
- store:dispatch(SetGameName(result.Name))
- return result.Name
- end,
- function()
- warn("Unable to get game name for Gamepad Menu")
- return Promise.reject()
- end)
- end
- script name:
- CorePackages.Packages._Index.Promise.Promise
- code:
- --[[
- An implementation of Promises similar to Promise/A+.
- ]]
- local ERROR_NON_PROMISE_IN_LIST = "Non-promise value passed into %s at index %s"
- local ERROR_NON_LIST = "Please pass a list of promises to %s"
- local ERROR_NON_FUNCTION = "Please pass a handler function to %s!"
- local MODE_KEY_METATABLE = {__mode = "k"}
- --[[
- Creates an enum dictionary with some metamethods to prevent common mistakes.
- ]]
- local function makeEnum(enumName, members)
- local enum = {}
- for _, memberName in ipairs(members) do
- enum[memberName] = memberName
- end
- return setmetatable(enum, {
- __index = function(_, k)
- error(string.format("%s is not in %s!", k, enumName), 2)
- end,
- __newindex = function()
- error(string.format("Creating new members in %s is not allowed!", enumName), 2)
- end,
- })
- end
- --[[
- An object to represent runtime errors that occur during execution.
- Promises that experience an error like this will be rejected with
- an instance of this object.
- ]]
- local Error do
- Error = {
- Kind = makeEnum("Promise.Error.Kind", {
- "ExecutionError",
- "AlreadyCancelled",
- "NotResolvedInTime",
- "TimedOut",
- }),
- }
- Error.__index = Error
- function Error.new(options, parent)
- options = options or {}
- return setmetatable({
- error = tostring(options.error) or "[This error has no error text.]",
- trace = options.trace,
- context = options.context,
- kind = options.kind,
- parent = parent,
- createdTick = os.clock(),
- createdTrace = debug.traceback(),
- }, Error)
- end
- function Error.is(anything)
- if type(anything) == "table" then
- local metatable = getmetatable(anything)
- if type(metatable) == "table" then
- return rawget(anything, "error") ~= nil and type(rawget(metatable, "extend")) == "function"
- end
- end
- return false
- end
- function Error.isKind(anything, kind)
- assert(kind ~= nil, "Argument #2 to Promise.Error.isKind must not be nil")
- return Error.is(anything) and anything.kind == kind
- end
- function Error:extend(options)
- options = options or {}
- options.kind = options.kind or self.kind
- return Error.new(options, self)
- end
- function Error:getErrorChain()
- local runtimeErrors = { self }
- while runtimeErrors[#runtimeErrors].parent do
- table.insert(runtimeErrors, runtimeErrors[#runtimeErrors].parent)
- end
- return runtimeErrors
- end
- function Error:__tostring()
- local errorStrings = {
- string.format("-- Promise.Error(%s) --", self.kind or "?"),
- }
- for _, runtimeError in ipairs(self:getErrorChain()) do
- table.insert(errorStrings, table.concat({
- runtimeError.trace or runtimeError.error,
- runtimeError.context,
- }, "\n"))
- end
- return table.concat(errorStrings, "\n")
- end
- end
- --[[
- Packs a number of arguments into a table and returns its length.
- Used to cajole varargs without dropping sparse values.
- ]]
- local function pack(...)
- return select("#", ...), { ... }
- end
- --[[
- Returns first value (success), and packs all following values.
- ]]
- local function packResult(success, ...)
- return success, select("#", ...), { ... }
- end
- local function makeErrorHandler(traceback)
- assert(traceback ~= nil)
- return function(err)
- -- If the error object is already a table, forward it directly.
- -- Should we extend the error here and add our own trace?
- if type(err) == "table" then
- return err
- end
- return Error.new({
- error = err,
- kind = Error.Kind.ExecutionError,
- trace = debug.traceback(tostring(err), 2),
- context = "Promise created at:\n\n" .. traceback,
- })
- end
- end
- --[[
- Calls a Promise executor with error handling.
- ]]
- local function runExecutor(traceback, callback, ...)
- return packResult(xpcall(callback, makeErrorHandler(traceback), ...))
- end
- --[[
- Creates a function that invokes a callback with correct error handling and
- resolution mechanisms.
- ]]
- local function createAdvancer(traceback, callback, resolve, reject)
- return function(...)
- local ok, resultLength, result = runExecutor(traceback, callback, ...)
- if ok then
- resolve(unpack(result, 1, resultLength))
- else
- reject(result[1])
- end
- end
- end
- local function isEmpty(t)
- return next(t) == nil
- end
- local Promise = {
- Error = Error,
- Status = makeEnum("Promise.Status", {"Started", "Resolved", "Rejected", "Cancelled"}),
- _getTime = os.clock,
- _timeEvent = game:GetService("RunService").Heartbeat,
- _unhandledRejectionCallbacks = {},
- }
- Promise.prototype = {}
- Promise.__index = Promise.prototype
- --[[
- Constructs a new Promise with the given initializing callback.
- This is generally only called when directly wrapping a non-promise API into
- a promise-based version.
- The callback will receive 'resolve' and 'reject' methods, used to start
- invoking the promise chain.
- Second parameter, parent, is used internally for tracking the "parent" in a
- promise chain. External code shouldn't need to worry about this.
- ]]
- function Promise._new(traceback, callback, parent)
- if parent ~= nil and not Promise.is(parent) then
- error("Argument #2 to Promise.new must be a promise or nil", 2)
- end
- local self = {
- -- Used to locate where a promise was created
- _source = traceback,
- _status = Promise.Status.Started,
- -- A table containing a list of all results, whether success or failure.
- -- Only valid if _status is set to something besides Started
- _values = nil,
- -- Lua doesn't like sparse arrays very much, so we explicitly store the
- -- length of _values to handle middle nils.
- _valuesLength = -1,
- -- Tracks if this Promise has no error observers..
- _unhandledRejection = true,
- -- Queues representing functions we should invoke when we update!
- _queuedResolve = {},
- _queuedReject = {},
- _queuedFinally = {},
- -- The function to run when/if this promise is cancelled.
- _cancellationHook = nil,
- -- The "parent" of this promise in a promise chain. Required for
- -- cancellation propagation upstream.
- _parent = parent,
- -- Consumers are Promises that have chained onto this one.
- -- We track them for cancellation propagation downstream.
- _consumers = setmetatable({}, MODE_KEY_METATABLE),
- }
- if parent and parent._status == Promise.Status.Started then
- parent._consumers[self] = true
- end
- setmetatable(self, Promise)
- local function resolve(...)
- self:_resolve(...)
- end
- local function reject(...)
- self:_reject(...)
- end
- local function onCancel(cancellationHook)
- if cancellationHook then
- if self._status == Promise.Status.Cancelled then
- cancellationHook()
- else
- self._cancellationHook = cancellationHook
- end
- end
- return self._status == Promise.Status.Cancelled
- end
- coroutine.wrap(function()
- local ok, _, result = runExecutor(
- self._source,
- callback,
- resolve,
- reject,
- onCancel
- )
- if not ok then
- reject(result[1])
- end
- end)()
- return self
- end
- function Promise.new(executor)
- return Promise._new(debug.traceback(nil, 2), executor)
- end
- function Promise:__tostring()
- return string.format("Promise(%s)", self._status)
- end
- --[[
- Promise.new, except pcall on a new thread is automatic.
- ]]
- function Promise.defer(callback)
- local traceback = debug.traceback(nil, 2)
- local promise
- promise = Promise._new(traceback, function(resolve, reject, onCancel)
- local connection
- connection = Promise._timeEvent:Connect(function()
- connection:Disconnect()
- local ok, _, result = runExecutor(traceback, callback, resolve, reject, onCancel)
- if not ok then
- reject(result[1])
- end
- end)
- end)
- return promise
- end
- -- Backwards compatibility
- Promise.async = Promise.defer
- --[[
- Create a promise that represents the immediately resolved value.
- ]]
- function Promise.resolve(...)
- local length, values = pack(...)
- return Promise._new(debug.traceback(nil, 2), function(resolve)
- resolve(unpack(values, 1, length))
- end)
- end
- --[[
- Create a promise that represents the immediately rejected value.
- ]]
- function Promise.reject(...)
- local length, values = pack(...)
- return Promise._new(debug.traceback(nil, 2), function(_, reject)
- reject(unpack(values, 1, length))
- end)
- end
- --[[
- Runs a non-promise-returning function as a Promise with the
- given arguments.
- ]]
- function Promise._try(traceback, callback, ...)
- local valuesLength, values = pack(...)
- return Promise._new(traceback, function(resolve)
- resolve(callback(unpack(values, 1, valuesLength)))
- end)
- end
- --[[
- Begins a Promise chain, turning synchronous errors into rejections.
- ]]
- function Promise.try(...)
- return Promise._try(debug.traceback(nil, 2), ...)
- end
- --[[
- Returns a new promise that:
- * is resolved when all input promises resolve
- * is rejected if ANY input promises reject
- ]]
- function Promise._all(traceback, promises, amount)
- if type(promises) ~= "table" then
- error(string.format(ERROR_NON_LIST, "Promise.all"), 3)
- end
- -- We need to check that each value is a promise here so that we can produce
- -- a proper error rather than a rejected promise with our error.
- for i, promise in pairs(promises) do
- if not Promise.is(promise) then
- error(string.format(ERROR_NON_PROMISE_IN_LIST, "Promise.all", tostring(i)), 3)
- end
- end
- -- If there are no values then return an already resolved promise.
- if #promises == 0 or amount == 0 then
- return Promise.resolve({})
- end
- return Promise._new(traceback, function(resolve, reject, onCancel)
- -- An array to contain our resolved values from the given promises.
- local resolvedValues = {}
- local newPromises = {}
- -- Keep a count of resolved promises because just checking the resolved
- -- values length wouldn't account for promises that resolve with nil.
- local resolvedCount = 0
- local rejectedCount = 0
- local done = false
- local function cancel()
- for _, promise in ipairs(newPromises) do
- promise:cancel()
- end
- end
- -- Called when a single value is resolved and resolves if all are done.
- local function resolveOne(i, ...)
- if done then
- return
- end
- resolvedCount = resolvedCount + 1
- if amount == nil then
- resolvedValues[i] = ...
- else
- resolvedValues[resolvedCount] = ...
- end
- if resolvedCount >= (amount or #promises) then
- done = true
- resolve(resolvedValues)
- cancel()
- end
- end
- onCancel(cancel)
- -- We can assume the values inside `promises` are all promises since we
- -- checked above.
- for i, promise in ipairs(promises) do
- newPromises[i] = promise:andThen(
- function(...)
- resolveOne(i, ...)
- end,
- function(...)
- rejectedCount = rejectedCount + 1
- if amount == nil or #promises - rejectedCount < amount then
- cancel()
- done = true
- reject(...)
- end
- end
- )
- end
- if done then
- cancel()
- end
- end)
- end
- function Promise.all(...)
- local promises = {...}
- -- check if we've been given a list of promises, not just a variable number of promises
- if type(promises[1]) == "table" and not Promise.is(promises[1]) then
- -- we've been given a table of promises already
- promises = promises[1]
- end
- return Promise._all(debug.traceback(nil, 2), promises)
- end
- function Promise.fold(list, callback, initialValue)
- assert(type(list) == "table", "Bad argument #1 to Promise.fold: must be a table")
- assert(type(callback) == "function", "Bad argument #2 to Promise.fold: must be a function")
- local accumulator = Promise.resolve(initialValue)
- return Promise.each(list, function(resolvedElement, i)
- accumulator = accumulator:andThen(function(previousValueResolved)
- return callback(previousValueResolved, resolvedElement, i)
- end)
- end):andThenReturn(accumulator)
- end
- function Promise.some(promises, amount)
- assert(type(amount) == "number", "Bad argument #2 to Promise.some: must be a number")
- return Promise._all(debug.traceback(nil, 2), promises, amount)
- end
- function Promise.any(promises)
- return Promise._all(debug.traceback(nil, 2), promises, 1):andThen(function(values)
- return values[1]
- end)
- end
- function Promise.allSettled(promises)
- if type(promises) ~= "table" then
- error(string.format(ERROR_NON_LIST, "Promise.allSettled"), 2)
- end
- -- We need to check that each value is a promise here so that we can produce
- -- a proper error rather than a rejected promise with our error.
- for i, promise in pairs(promises) do
- if not Promise.is(promise) then
- error(string.format(ERROR_NON_PROMISE_IN_LIST, "Promise.allSettled", tostring(i)), 2)
- end
- end
- -- If there are no values then return an already resolved promise.
- if #promises == 0 then
- return Promise.resolve({})
- end
- return Promise._new(debug.traceback(nil, 2), function(resolve, _, onCancel)
- -- An array to contain our resolved values from the given promises.
- local fates = {}
- local newPromises = {}
- -- Keep a count of resolved promises because just checking the resolved
- -- values length wouldn't account for promises that resolve with nil.
- local finishedCount = 0
- -- Called when a single value is resolved and resolves if all are done.
- local function resolveOne(i, ...)
- finishedCount = finishedCount + 1
- fates[i] = ...
- if finishedCount >= #promises then
- resolve(fates)
- end
- end
- onCancel(function()
- for _, promise in ipairs(newPromises) do
- promise:cancel()
- end
- end)
- -- We can assume the values inside `promises` are all promises since we
- -- checked above.
- for i, promise in ipairs(promises) do
- newPromises[i] = promise:finally(
- function(...)
- resolveOne(i, ...)
- end
- )
- end
- end)
- end
- --[[
- Races a set of Promises and returns the first one that resolves,
- cancelling the others.
- ]]
- function Promise.race(promises)
- assert(type(promises) == "table", string.format(ERROR_NON_LIST, "Promise.race"))
- for i, promise in pairs(promises) do
- assert(Promise.is(promise), string.format(ERROR_NON_PROMISE_IN_LIST, "Promise.race", tostring(i)))
- end
- return Promise._new(debug.traceback(nil, 2), function(resolve, reject, onCancel)
- local newPromises = {}
- local finished = false
- local function cancel()
- for _, promise in ipairs(newPromises) do
- promise:cancel()
- end
- end
- local function finalize(callback)
- return function (...)
- cancel()
- finished = true
- return callback(...)
- end
- end
- if onCancel(finalize(reject)) then
- return
- end
- for i, promise in ipairs(promises) do
- newPromises[i] = promise:andThen(finalize(resolve), finalize(reject))
- end
- if finished then
- cancel()
- end
- end)
- end
- --[[
- Iterates serially over the given an array of values, calling the predicate callback on each before continuing.
- If the predicate returns a Promise, we wait for that Promise to resolve before continuing to the next item
- in the array. If the Promise the predicate returns rejects, the Promise from Promise.each is also rejected with
- the same value.
- Returns a Promise containing an array of the return values from the predicate for each item in the original list.
- ]]
- function Promise.each(list, predicate)
- assert(type(list) == "table", string.format(ERROR_NON_LIST, "Promise.each"))
- assert(type(predicate) == "function", string.format(ERROR_NON_FUNCTION, "Promise.each"))
- return Promise._new(debug.traceback(nil, 2), function(resolve, reject, onCancel)
- local results = {}
- local promisesToCancel = {}
- local cancelled = false
- local function cancel()
- for _, promiseToCancel in ipairs(promisesToCancel) do
- promiseToCancel:cancel()
- end
- end
- onCancel(function()
- cancelled = true
- cancel()
- end)
- -- We need to preprocess the list of values and look for Promises.
- -- If we find some, we must register our andThen calls now, so that those Promises have a consumer
- -- from us registered. If we don't do this, those Promises might get cancelled by something else
- -- before we get to them in the series because it's not possible to tell that we plan to use it
- -- unless we indicate it here.
- local preprocessedList = {}
- for index, value in ipairs(list) do
- if Promise.is(value) then
- if value:getStatus() == Promise.Status.Cancelled then
- cancel()
- return reject(Error.new({
- error = "Promise is cancelled",
- kind = Error.Kind.AlreadyCancelled,
- context = string.format(
- "The Promise that was part of the array at index %d passed into Promise.each was already cancelled when Promise.each began.\n\nThat Promise was created at:\n\n%s",
- index,
- value._source
- ),
- }))
- elseif value:getStatus() == Promise.Status.Rejected then
- cancel()
- return reject(select(2, value:await()))
- end
- -- Chain a new Promise from this one so we only cancel ours
- local ourPromise = value:andThen(function(...)
- return ...
- end)
- table.insert(promisesToCancel, ourPromise)
- preprocessedList[index] = ourPromise
- else
- preprocessedList[index] = value
- end
- end
- for index, value in ipairs(preprocessedList) do
- if Promise.is(value) then
- local success
- success, value = value:await()
- if not success then
- cancel()
- return reject(value)
- end
- end
- if cancelled then
- return
- end
- local predicatePromise = Promise.resolve(predicate(value, index))
- table.insert(promisesToCancel, predicatePromise)
- local success, result = predicatePromise:await()
- if not success then
- cancel()
- return reject(result)
- end
- results[index] = result
- end
- resolve(results)
- end)
- end
- --[[
- Is the given object a Promise instance?
- ]]
- function Promise.is(object)
- if type(object) ~= "table" then
- return false
- end
- local objectMetatable = getmetatable(object)
- if objectMetatable == Promise then
- -- The Promise came from this library.
- return true
- elseif objectMetatable == nil then
- -- No metatable, but we should still chain onto tables with andThen methods
- return type(object.andThen) == "function"
- elseif
- type(objectMetatable) == "table"
- and type(rawget(objectMetatable, "__index")) == "table"
- and type(rawget(rawget(objectMetatable, "__index"), "andThen")) == "function"
- then
- -- Maybe this came from a different or older Promise library.
- return true
- end
- return false
- end
- --[[
- Converts a yielding function into a Promise-returning one.
- ]]
- function Promise.promisify(callback)
- return function(...)
- return Promise._try(debug.traceback(nil, 2), callback, ...)
- end
- end
- --[[
- Creates a Promise that resolves after given number of seconds.
- ]]
- do
- -- uses a sorted doubly linked list (queue) to achieve O(1) remove operations and O(n) for insert
- -- the initial node in the linked list
- local first
- local connection
- function Promise.delay(seconds)
- assert(type(seconds) == "number", "Bad argument #1 to Promise.delay, must be a number.")
- -- If seconds is -INF, INF, NaN, or less than 1 / 60, assume seconds is 1 / 60.
- -- This mirrors the behavior of wait()
- if not (seconds >= 1 / 60) or seconds == math.huge then
- seconds = 1 / 60
- end
- return Promise._new(debug.traceback(nil, 2), function(resolve, _, onCancel)
- local startTime = Promise._getTime()
- local endTime = startTime + seconds
- local node = {
- resolve = resolve,
- startTime = startTime,
- endTime = endTime,
- }
- if connection == nil then -- first is nil when connection is nil
- first = node
- connection = Promise._timeEvent:Connect(function()
- local threadStart = Promise._getTime()
- while first ~= nil and first.endTime < threadStart do
- local current = first
- first = current.next
- if first == nil then
- connection:Disconnect()
- connection = nil
- else
- first.previous = nil
- end
- current.resolve(Promise._getTime() - current.startTime)
- end
- end)
- else -- first is non-nil
- if first.endTime < endTime then -- if `node` should be placed after `first`
- -- we will insert `node` between `current` and `next`
- -- (i.e. after `current` if `next` is nil)
- local current = first
- local next = current.next
- while next ~= nil and next.endTime < endTime do
- current = next
- next = current.next
- end
- -- `current` must be non-nil, but `next` could be `nil` (i.e. last item in list)
- current.next = node
- node.previous = current
- if next ~= nil then
- node.next = next
- next.previous = node
- end
- else
- -- set `node` to `first`
- node.next = first
- first.previous = node
- first = node
- end
- end
- onCancel(function()
- -- remove node from queue
- local next = node.next
- if first == node then
- if next == nil then -- if `node` is the first and last
- connection:Disconnect()
- connection = nil
- else -- if `node` is `first` and not the last
- next.previous = nil
- end
- first = next
- else
- local previous = node.previous
- -- since `node` is not `first`, then we know `previous` is non-nil
- previous.next = next
- if next ~= nil then
- next.previous = previous
- end
- end
- end)
- end)
- end
- end
- --[[
- Rejects the promise after `seconds` seconds.
- ]]
- function Promise.prototype:timeout(seconds, rejectionValue)
- local traceback = debug.traceback(nil, 2)
- return Promise.race({
- Promise.delay(seconds):andThen(function()
- return Promise.reject(rejectionValue == nil and Error.new({
- kind = Error.Kind.TimedOut,
- error = "Timed out",
- context = string.format(
- "Timeout of %d seconds exceeded.\n:timeout() called at:\n\n%s",
- seconds,
- traceback
- ),
- }) or rejectionValue)
- end),
- self,
- })
- end
- function Promise.prototype:getStatus()
- return self._status
- end
- --[[
- Creates a new promise that receives the result of this promise.
- The given callbacks are invoked depending on that result.
- ]]
- function Promise.prototype:_andThen(traceback, successHandler, failureHandler)
- self._unhandledRejection = false
- -- Create a new promise to follow this part of the chain
- return Promise._new(traceback, function(resolve, reject)
- -- Our default callbacks just pass values onto the next promise.
- -- This lets success and failure cascade correctly!
- local successCallback = resolve
- if successHandler then
- successCallback = createAdvancer(
- traceback,
- successHandler,
- resolve,
- reject
- )
- end
- local failureCallback = reject
- if failureHandler then
- failureCallback = createAdvancer(
- traceback,
- failureHandler,
- resolve,
- reject
- )
- end
- if self._status == Promise.Status.Started then
- -- If we haven't resolved yet, put ourselves into the queue
- table.insert(self._queuedResolve, successCallback)
- table.insert(self._queuedReject, failureCallback)
- elseif self._status == Promise.Status.Resolved then
- -- This promise has already resolved! Trigger success immediately.
- successCallback(unpack(self._values, 1, self._valuesLength))
- elseif self._status == Promise.Status.Rejected then
- -- This promise died a terrible death! Trigger failure immediately.
- failureCallback(unpack(self._values, 1, self._valuesLength))
- elseif self._status == Promise.Status.Cancelled then
- -- We don't want to call the success handler or the failure handler,
- -- we just reject this promise outright.
- reject(Error.new({
- error = "Promise is cancelled",
- kind = Error.Kind.AlreadyCancelled,
- context = "Promise created at\n\n" .. traceback,
- }))
- end
- end, self)
- end
- function Promise.prototype:andThen(successHandler, failureHandler)
- assert(
- successHandler == nil or type(successHandler) == "function" or successHandler.__call ~= nil,
- string.format(ERROR_NON_FUNCTION, "Promise:andThen")
- )
- assert(
- failureHandler == nil or type(failureHandler) == "function" or failureHandler.__call ~= nil,
- string.format(ERROR_NON_FUNCTION, "Promise:andThen")
- )
- return self:_andThen(debug.traceback(nil, 2), successHandler, failureHandler)
- end
- --[[
- Used to catch any errors that may have occurred in the promise.
- ]]
- function Promise.prototype:catch(failureCallback)
- assert(
- failureCallback == nil or type(failureCallback) == "function" or failureCallback.__call ~= nil,
- string.format(ERROR_NON_FUNCTION, "Promise:catch")
- )
- return self:_andThen(debug.traceback(nil, 2), nil, failureCallback)
- end
- --[[
- Like andThen, but the value passed into the handler is also the
- value returned from the handler.
- ]]
- function Promise.prototype:tap(tapCallback)
- assert(type(tapCallback) == "function", string.format(ERROR_NON_FUNCTION, "Promise:tap"))
- return self:_andThen(debug.traceback(nil, 2), function(...)
- local callbackReturn = tapCallback(...)
- if Promise.is(callbackReturn) then
- local length, values = pack(...)
- return callbackReturn:andThen(function()
- return unpack(values, 1, length)
- end)
- end
- return ...
- end)
- end
- --[[
- Calls a callback on `andThen` with specific arguments.
- ]]
- function Promise.prototype:andThenCall(callback, ...)
- assert(type(callback) == "function", string.format(ERROR_NON_FUNCTION, "Promise:andThenCall"))
- local length, values = pack(...)
- return self:_andThen(debug.traceback(nil, 2), function()
- return callback(unpack(values, 1, length))
- end)
- end
- --[[
- Shorthand for an andThen handler that returns the given value.
- ]]
- function Promise.prototype:andThenReturn(...)
- local length, values = pack(...)
- return self:_andThen(debug.traceback(nil, 2), function()
- return unpack(values, 1, length)
- end)
- end
- --[[
- Cancels the promise, disallowing it from rejecting or resolving, and calls
- the cancellation hook if provided.
- ]]
- function Promise.prototype:cancel()
- if self._status ~= Promise.Status.Started then
- return
- end
- self._status = Promise.Status.Cancelled
- if self._cancellationHook then
- self._cancellationHook()
- end
- if self._parent then
- self._parent:_consumerCancelled(self)
- end
- for child in pairs(self._consumers) do
- child:cancel()
- end
- self:_finalize()
- end
- --[[
- Used to decrease the number of consumers by 1, and if there are no more,
- cancel this promise.
- ]]
- function Promise.prototype:_consumerCancelled(consumer)
- if self._status ~= Promise.Status.Started then
- return
- end
- self._consumers[consumer] = nil
- if next(self._consumers) == nil then
- self:cancel()
- end
- end
- --[[
- Used to set a handler for when the promise resolves, rejects, or is
- cancelled. Returns a new promise chained from this promise.
- ]]
- function Promise.prototype:_finally(traceback, finallyHandler, onlyOk)
- if not onlyOk then
- self._unhandledRejection = false
- end
- -- Return a promise chained off of this promise
- return Promise._new(traceback, function(resolve, reject)
- local finallyCallback = resolve
- if finallyHandler then
- finallyCallback = createAdvancer(
- traceback,
- finallyHandler,
- resolve,
- reject
- )
- end
- if onlyOk then
- local callback = finallyCallback
- finallyCallback = function(...)
- if self._status == Promise.Status.Rejected then
- return resolve(self)
- end
- return callback(...)
- end
- end
- if self._status == Promise.Status.Started then
- -- The promise is not settled, so queue this.
- table.insert(self._queuedFinally, finallyCallback)
- else
- -- The promise already settled or was cancelled, run the callback now.
- finallyCallback(self._status)
- end
- end, self)
- end
- function Promise.prototype:finally(finallyHandler)
- assert(
- finallyHandler == nil or type(finallyHandler) == "function" or finallyHandler.__call ~= nill,
- string.format(ERROR_NON_FUNCTION, "Promise:finally")
- )
- return self:_finally(debug.traceback(nil, 2), finallyHandler)
- end
- --[[
- Calls a callback on `finally` with specific arguments.
- ]]
- function Promise.prototype:finallyCall(callback, ...)
- assert(type(callback) == "function", string.format(ERROR_NON_FUNCTION, "Promise:finallyCall"))
- local length, values = pack(...)
- return self:_finally(debug.traceback(nil, 2), function()
- return callback(unpack(values, 1, length))
- end)
- end
- --[[
- Shorthand for a finally handler that returns the given value.
- ]]
- function Promise.prototype:finallyReturn(...)
- local length, values = pack(...)
- return self:_finally(debug.traceback(nil, 2), function()
- return unpack(values, 1, length)
- end)
- end
- --[[
- Similar to finally, except rejections are propagated through it.
- ]]
- function Promise.prototype:done(finallyHandler)
- assert(
- finallyHandler == nil or type(finallyHandler) == "function" or finallyHandler.__call ~= nill,
- string.format(ERROR_NON_FUNCTION, "Promise:done")
- )
- return self:_finally(debug.traceback(nil, 2), finallyHandler, true)
- end
- --[[
- Calls a callback on `done` with specific arguments.
- ]]
- function Promise.prototype:doneCall(callback, ...)
- assert(type(callback) == "function", string.format(ERROR_NON_FUNCTION, "Promise:doneCall"))
- local length, values = pack(...)
- return self:_finally(debug.traceback(nil, 2), function()
- return callback(unpack(values, 1, length))
- end, true)
- end
- --[[
- Shorthand for a done handler that returns the given value.
- ]]
- function Promise.prototype:doneReturn(...)
- local length, values = pack(...)
- return self:_finally(debug.traceback(nil, 2), function()
- return unpack(values, 1, length)
- end, true)
- end
- --[[
- Yield until the promise is completed.
- This matches the execution model of normal Roblox functions.
- ]]
- function Promise.prototype:awaitStatus()
- self._unhandledRejection = false
- if self._status == Promise.Status.Started then
- local bindable = Instance.new("BindableEvent")
- self:finally(function()
- bindable:Fire()
- end)
- bindable.Event:Wait()
- bindable:Destroy()
- end
- if self._status == Promise.Status.Resolved then
- return self._status, unpack(self._values, 1, self._valuesLength)
- elseif self._status == Promise.Status.Rejected then
- return self._status, unpack(self._values, 1, self._valuesLength)
- end
- return self._status
- end
- local function awaitHelper(status, ...)
- return status == Promise.Status.Resolved, ...
- end
- --[[
- Calls awaitStatus internally, returns (isResolved, values...)
- ]]
- function Promise.prototype:await()
- return awaitHelper(self:awaitStatus())
- end
- local function expectHelper(status, ...)
- if status ~= Promise.Status.Resolved then
- error((...) == nil and "Expected Promise rejected with no value." or (...), 3)
- end
- return ...
- end
- --[[
- Calls await and only returns if the Promise resolves.
- Throws if the Promise rejects or gets cancelled.
- ]]
- function Promise.prototype:expect()
- return expectHelper(self:awaitStatus())
- end
- -- Backwards compatibility
- Promise.prototype.awaitValue = Promise.prototype.expect
- --[[
- Intended for use in tests.
- Similar to await(), but instead of yielding if the promise is unresolved,
- _unwrap will throw. This indicates an assumption that a promise has
- resolved.
- ]]
- function Promise.prototype:_unwrap()
- if self._status == Promise.Status.Started then
- error("Promise has not resolved or rejected.", 2)
- end
- local success = self._status == Promise.Status.Resolved
- return success, unpack(self._values, 1, self._valuesLength)
- end
- function Promise.prototype:_resolve(...)
- if self._status ~= Promise.Status.Started then
- if Promise.is((...)) then
- (...):_consumerCancelled(self)
- end
- return
- end
- -- If the resolved value was a Promise, we chain onto it!
- if Promise.is((...)) then
- -- Without this warning, arguments sometimes mysteriously disappear
- if select("#", ...) > 1 then
- local message = string.format(
- "When returning a Promise from andThen, extra arguments are " ..
- "discarded! See:\n\n%s",
- self._source
- )
- warn(message)
- end
- local chainedPromise = ...
- local promise = chainedPromise:andThen(
- function(...)
- self:_resolve(...)
- end,
- function(...)
- local maybeRuntimeError = chainedPromise._values[1]
- -- Backwards compatibility < v2
- if chainedPromise._error then
- maybeRuntimeError = Error.new({
- error = chainedPromise._error,
- kind = Error.Kind.ExecutionError,
- context = "[No stack trace available as this Promise originated from an older version of the Promise library (< v2)]",
- })
- end
- if Error.isKind(maybeRuntimeError, Error.Kind.ExecutionError) then
- return self:_reject(maybeRuntimeError:extend({
- error = "This Promise was chained to a Promise that errored.",
- trace = "",
- context = string.format(
- "The Promise at:\n\n%s\n...Rejected because it was chained to the following Promise, which encountered an error:\n",
- self._source
- ),
- }))
- end
- self:_reject(...)
- end
- )
- if promise._status == Promise.Status.Cancelled then
- self:cancel()
- elseif promise._status == Promise.Status.Started then
- -- Adopt ourselves into promise for cancellation propagation.
- self._parent = promise
- promise._consumers[self] = true
- end
- return
- end
- self._status = Promise.Status.Resolved
- self._valuesLength, self._values = pack(...)
- -- We assume that these callbacks will not throw errors.
- for _, callback in ipairs(self._queuedResolve) do
- coroutine.wrap(callback)(...)
- end
- self:_finalize()
- end
- function Promise.prototype:_reject(...)
- if self._status ~= Promise.Status.Started then
- return
- end
- self._status = Promise.Status.Rejected
- self._valuesLength, self._values = pack(...)
- -- If there are any rejection handlers, call those!
- if not isEmpty(self._queuedReject) then
- -- We assume that these callbacks will not throw errors.
- for _, callback in ipairs(self._queuedReject) do
- coroutine.wrap(callback)(...)
- end
- else
- -- At this point, no one was able to observe the error.
- -- An error handler might still be attached if the error occurred
- -- synchronously. We'll wait one tick, and if there are still no
- -- observers, then we should put a message in the console.
- local err = tostring((...))
- coroutine.wrap(function()
- Promise._timeEvent:Wait()
- -- Someone observed the error, hooray!
- if not self._unhandledRejection then
- return
- end
- -- Build a reasonable message
- local message = string.format(
- "Unhandled Promise rejection:\n\n%s\n\n%s",
- err,
- self._source
- )
- for _, callback in ipairs(Promise._unhandledRejectionCallbacks) do
- task.spawn(callback, self, unpack(self._values, 1, self._valuesLength))
- end
- if Promise.TEST then
- -- Don't spam output when we're running tests.
- return
- end
- warn(message)
- end)()
- end
- self:_finalize()
- end
- --[[
- Calls any :finally handlers. We need this to be a separate method and
- queue because we must call all of the finally callbacks upon a success,
- failure, *and* cancellation.
- ]]
- function Promise.prototype:_finalize()
- for _, callback in ipairs(self._queuedFinally) do
- -- Purposefully not passing values to callbacks here, as it could be the
- -- resolved values, or rejected errors. If the developer needs the values,
- -- they should use :andThen or :catch explicitly.
- coroutine.wrap(callback)(self._status)
- end
- self._queuedFinally = nil
- self._queuedReject = nil
- self._queuedResolve = nil
- -- Clear references to other Promises to allow gc
- if not Promise.TEST then
- self._parent = nil
- self._consumers = nil
- end
- end
- --[[
- Chains a Promise from this one that is resolved if this Promise is
- resolved, and rejected if it is not resolved.
- ]]
- function Promise.prototype:now(rejectionValue)
- local traceback = debug.traceback(nil, 2)
- if self._status == Promise.Status.Resolved then
- return self:_andThen(traceback, function(...)
- return ...
- end)
- else
- return Promise.reject(rejectionValue == nil and Error.new({
- kind = Error.Kind.NotResolvedInTime,
- error = "This Promise was not resolved in time for :now()",
- context = ":now() was called at:\n\n" .. traceback,
- }) or rejectionValue)
- end
- end
- --[[
- Retries a Promise-returning callback N times until it succeeds.
- ]]
- function Promise.retry(callback, times, ...)
- assert(type(callback) == "function", "Parameter #1 to Promise.retry must be a function")
- assert(type(times) == "number", "Parameter #2 to Promise.retry must be a number")
- local args, length = {...}, select("#", ...)
- return Promise.resolve(callback(...)):catch(function(...)
- if times > 0 then
- return Promise.retry(callback, times - 1, unpack(args, 1, length))
- else
- return Promise.reject(...)
- end
- end)
- end
- --[[
- Converts an event into a Promise with an optional predicate
- ]]
- function Promise.fromEvent(event, predicate)
- predicate = predicate or function()
- return true
- end
- return Promise._new(debug.traceback(nil, 2), function(resolve, _, onCancel)
- local connection
- local shouldDisconnect = false
- local function disconnect()
- connection:Disconnect()
- connection = nil
- end
- -- We use shouldDisconnect because if the callback given to Connect is called before
- -- Connect returns, connection will still be nil. This happens with events that queue up
- -- events when there's nothing connected, such as RemoteEvents
- connection = event:Connect(function(...)
- local callbackValue = predicate(...)
- if callbackValue == true then
- resolve(...)
- if connection then
- disconnect()
- else
- shouldDisconnect = true
- end
- elseif type(callbackValue) ~= "boolean" then
- error("Promise.fromEvent predicate should always return a boolean")
- end
- end)
- if shouldDisconnect and connection then
- return disconnect()
- end
- onCancel(disconnect)
- end)
- end
- --[=[
- Registers a callback that runs when an unhandled rejection happens. An unhandled rejection happens when a Promise
- is rejected, and the rejection is not observed with `:catch`.
- The callback is called with the actual promise that rejected, followed by the rejection values.
- @param callback (promise: Promise, ...: any) -- A callback that runs when an unhandled rejection happens.
- @return () -> () -- Function that unregisters the `callback` when called
- ]=]
- function Promise.onUnhandledRejection(callback)
- table.insert(Promise._unhandledRejectionCallbacks, callback)
- return function()
- local index = table.find(Promise._unhandledRejectionCallbacks, callback)
- if index then
- table.remove(Promise._unhandledRejectionCallbacks, index)
- end
- end
- end
- return Promise
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement