Add Promise.delay, :timeout

Closes #8
Closes #7
This commit is contained in:
Eryn Lynn 2019-09-29 00:07:32 -04:00
parent f32ebd35bb
commit cef64024e6
3 changed files with 102 additions and 1 deletions

View file

@ -10,6 +10,7 @@
- Add Promise.try
- Add `done`, `doneCall`, `doneReturn`
- Add `andThenReturn`, `finallyReturn`
- Add `Promise.delay`, `promise:timeout`
# 2.4.0

View file

@ -220,6 +220,18 @@ docs:
static: true
params: "promises: array<Promise<T>>"
returns: Promise<T>
- name: delay
desc: |
Returns a Promise that resolves after `seconds` seconds have passed. The Promise resolves with the actual amount of time that was waited.
This function is **not** a wrapper around `wait`. `Promise.delay` uses a custom scheduler which provides more accurate timing. As an optimization, cancelling this Promise instantly removes the task from the scheduler.
::: warning
Passing `NaN`, infinity, or a number less than 1/60 is equivalent to passing 1/60.
:::
params: "seconds: number"
returns: Promise<number>
static: true
- name: is
desc: Returns whether the given object is a Promise. This only checks if the object is a table and has an `andThen` method.
static: true
@ -473,12 +485,27 @@ docs:
desc: Values to return from the function.
returns: Promise<...any?>
- name: timeout
params: "seconds: number, rejectionValue: any?"
desc: |
Returns a new Promise that resolves if the chained Promise resolves within `seconds` seconds, or rejects if execution time exceeds `seconds`. The chained Promise will be cancelled if the timeout is reached.
Sugar for:
```lua
Promise.race({
Promise.delay(seconds):andThen(function()
return Promise.reject(rejectionValue == nil and "Timed out" or rejectionValue)
end),
promise
})
```
- name: cancel
desc: |
Cancels this promise, preventing the promise from resolving or rejecting. Does not do anything if the promise is already settled.
Cancellations will propagate upwards through chained promises.
Cancellations will propagate upwards and downwards through chained promises.
Promises will only be cancelled if all of their consumers are also cancelled. This is to say that if you call `andThen` twice on the same promise, and you cancel only one of the child promises, it will not cancel the parent promise until the other child promise is also cancelled.

View file

@ -392,6 +392,79 @@ function Promise.promisify(callback)
end
end
--[[
Creates a Promise that resolves after given number of seconds.
]]
do
local connection
local queue = {}
local function enqueue(callback, seconds)
table.insert(queue, {
callback = callback,
startTime = tick(),
endTime = tick() + math.max(seconds, 1/60)
})
table.sort(queue, function(a, b)
return a.endTime < b.endTime
end)
if not connection then
connection = RunService.Heartbeat:Connect(function()
while #queue > 0 and queue[1].endTime <= tick() do
local item = table.remove(queue, 1)
item.callback(tick() - item.startTime)
end
if #queue == 0 then
connection:Disconnect()
connection = nil
end
end)
end
end
local function dequeue(callback)
for i, item in ipairs(queue) do
if item.callback == callback then
table.remove(queue, i)
break
end
end
end
function Promise.delay(seconds)
assert(type(seconds) == "number", "Bad argument #1 to Promise.delay, must be a number.")
-- If seconds is -INF, INF, or NaN, assume seconds is 0.
-- This mirrors the behavior of wait()
if seconds < 0 or seconds == math.huge or seconds ~= seconds then
seconds = 0
end
return Promise.new(function(resolve, _, onCancel)
enqueue(resolve, seconds)
onCancel(function()
dequeue(resolve)
end)
end)
end
end
--[[
Rejects the promise after `seconds` seconds.
]]
function Promise.prototype:timeout(seconds, timeoutValue)
return Promise.race({
Promise.delay(seconds):andThen(function()
return Promise.reject(timeoutValue == nil and "Timed out" or timeoutValue)
end),
self
})
end
function Promise.prototype:getStatus()
return self._status
end