Add Promise.fromEvent

closes #14
This commit is contained in:
Eryn Lynn 2020-05-29 02:10:45 -04:00
parent e5843cf5a2
commit b4ba6d2414
3 changed files with 122 additions and 0 deletions

View file

@ -369,6 +369,48 @@ docs:
returns: Promise<T>
static: true
- name: fromEvent
desc: |
Converts an event into a Promise which resolves the next time the event fires.
The optional `predicate` callback, if passed, will receive the event arguments and should return `true` or `false`, based on if this fired event should resolve the Promise or not. If `true`, the Promise resolves. If `false`, nothing happens and the predicate will be rerun the next time the event fires.
The Promise will resolve with the event arguments.
::: tip
This function will work given any object with a `Connect` method. This includes all Roblox events.
:::
```lua
-- Creates a Promise which only resolves when `somePart` is touched by a part named `"Something specific"`.
return Promise.fromEvent(somePart.Touched, function(part)
return part.Name == "Something specific"
end)
```
params:
- name: event
type:
kind: interface
type:
Connect:
type:
kind: function
params:
- name: callback
type:
kind: function
params: "...: P"
desc: Any object with a `Connect` method. This includes all Roblox events.
- name: predicate
optional: true
type:
kind: function
params: "...: P"
returns: boolean
desc: A function which determines if the Promise should resolve with the given value, or wait for the next event to check again.
returns: Promise<P>
static: true
- name: is
desc: Checks whether the given object is a Promise via duck typing. This only checks if the object is a table and has an `andThen` method.
static: true

View file

@ -1341,4 +1341,51 @@ function Promise.retry(callback, times, ...)
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, reject, 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(function()
disconnect()
end)
end)
end
return Promise

View file

@ -1441,4 +1441,37 @@ return function()
end)
end)
describe("Promise.fromEvent", function()
it("should convert a Promise into an event", function()
local event = Instance.new("BindableEvent")
local promise = Promise.fromEvent(event.Event)
expect(promise:getStatus()).to.equal(Promise.Status.Started)
event:Fire("foo")
expect(promise:getStatus()).to.equal(Promise.Status.Resolved)
expect(promise._values[1]).to.equal("foo")
end)
it("should convert a Promise into an event with the predicate", function()
local event = Instance.new("BindableEvent")
local promise = Promise.fromEvent(event.Event, function(param)
return param == "foo"
end)
expect(promise:getStatus()).to.equal(Promise.Status.Started)
event:Fire("bar")
expect(promise:getStatus()).to.equal(Promise.Status.Started)
event:Fire("foo")
expect(promise:getStatus()).to.equal(Promise.Status.Resolved)
expect(promise._values[1]).to.equal("foo")
end)
end)
end