From a6a97749354ca976ee518bc44148c47cfb1f16ef Mon Sep 17 00:00:00 2001 From: Rahat Ahmed Date: Thu, 24 Mar 2016 16:54:35 -0500 Subject: [PATCH] Add once and preemptOnce methods --- spec/emitter-spec.coffee | 33 +++++++++++++++++++++++++++++++ src/emitter.coffee | 42 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/spec/emitter-spec.coffee b/spec/emitter-spec.coffee index e46555e..b1b9aa5 100644 --- a/spec/emitter-spec.coffee +++ b/spec/emitter-spec.coffee @@ -44,6 +44,39 @@ describe "Emitter", -> emitter.emit 'foo', 2 expect(events).toEqual [] + it "invokes observers registered with once when the named event is emitted only once unless disposed", -> + emitter = new Emitter + + fooEvents = [] + barEvents = [] + quxEvents = [] + + sub1 = emitter.once 'foo', (value) -> fooEvents.push(['a', value]) + sub2 = emitter.once 'bar', (value) -> barEvents.push(['b', value]) + sub3 = emitter.preemptOnce 'bar', (value) -> barEvents.push(['c', value]) + sub4 = emitter.once 'qux', (value) -> quxEvents.push(['c', value]) + + emitter.emit 'foo', 1 + emitter.emit 'foo', 2 + emitter.emit 'bar', 3 + + sub1.dispose() + + emitter.emit 'foo', 4 + emitter.emit 'bar', 5 + + sub2.dispose() + + emitter.emit 'bar', 6 + + sub4.dispose() + + emitter.emit 'qux', 7 + + expect(fooEvents).toEqual [['a', 1]] + expect(barEvents).toEqual [['c', 3], ['b', 3]] + expect(quxEvents).toEqual [] + describe "when a handler throws an exception", -> describe "when no exception handlers are registered on Emitter", -> it "throws exceptions as normal, stopping subsequent handlers from firing", -> diff --git a/src/emitter.coffee b/src/emitter.coffee index e047cbd..f92766f 100644 --- a/src/emitter.coffee +++ b/src/emitter.coffee @@ -101,6 +101,27 @@ class Emitter new Disposable(@off.bind(this, eventName, handler)) + # Public: Register the given handler function to be invoked only once whenever + # the next by the given name are emitted via {::emit}. After being invoked, + # will automatically be unsubscribed. + # + # * `eventName` {String} naming the event that you want to invoke the handler + # when emitted. + # * `handler` {Function} to invoke when {::emit} is called with the given + # event name. + # + # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + once: (eventName, handler, unshift=false) -> + if typeof handler isnt 'function' + throw new Error("Handler must be a function") + + subscription = null + wrappedHandler = (value) -> + handler(value) + subscription.dispose() + + subscription = @on(eventName, wrappedHandler, unshift) + # Public: Register the given handler function to be invoked *before* all # other handlers existing at the time of subscription whenever events by the # given name are emitted via {::emit}. @@ -121,6 +142,27 @@ class Emitter preempt: (eventName, handler) -> @on(eventName, handler, true) + # Public: Register the given handler function to be invoked *before* all + # other handlers existing at the time of subscription only once the next time + # events by the given name are emitted via {::emit}. After being invoked, it + # will automatically be unsubscribed. + # + # Use this method when you need to be the first to handle a given event. This + # could be required when a data structure in a parent object needs to be + # updated before third-party event handlers registered on a child object via a + # public API are invoked. Your handler could itself be preempted via + # subsequent calls to this method, but this can be controlled by keeping + # methods based on `::preempt` private. + # + # * `eventName` {String} naming the event that you want to invoke the handler + # when emitted. + # * `handler` {Function} to invoke when {::emit} is called with the given + # event name. + # + # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + preemptOnce: (eventName, handler) -> + @once(eventName, handler, true) + # Private: Used by the disposable. off: (eventName, handlerToRemove) -> return if @disposed