From b4ba6d2414eeba18cc49621127962accc096f845 Mon Sep 17 00:00:00 2001 From: Eryn Lynn Date: Fri, 29 May 2020 02:10:45 -0400 Subject: [PATCH] Add Promise.fromEvent closes #14 --- lib/README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ lib/init.lua | 47 +++++++++++++++++++++++++++++++++++++++++++++++ lib/init.spec.lua | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+) diff --git a/lib/README.md b/lib/README.md index d885f5a..c61105f 100644 --- a/lib/README.md +++ b/lib/README.md @@ -369,6 +369,48 @@ docs: returns: Promise 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

+ 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 diff --git a/lib/init.lua b/lib/init.lua index d3ef75c..59f66a1 100644 --- a/lib/init.lua +++ b/lib/init.lua @@ -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 diff --git a/lib/init.spec.lua b/lib/init.spec.lua index 9044917..6780d15 100644 --- a/lib/init.spec.lua +++ b/lib/init.spec.lua @@ -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 \ No newline at end of file