Skip to content
This repository was archived by the owner on Dec 15, 2022. It is now read-only.

Commit 901dea7

Browse files
Add once and preemptOnce methods
1 parent ec79327 commit 901dea7

3 files changed

Lines changed: 88 additions & 0 deletions

File tree

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,19 @@ subscriptions.add user2.onDidChangeName (name) -> console.log("User 2: #{name}")
5555
subscriptions.dispose()
5656
```
5757

58+
## Subscription Convenience Functions
59+
60+
In addition to the above subscription functions, there are a few convenience
61+
functions available.
62+
63+
`Emitter::preempt` is identical to `::on`, but will register the handler to be
64+
invoked *before* all other handlers previously registered.
65+
66+
`Emitter::once` is also identical to `::on`, but will automatically unsubscribe
67+
the handler after it is invoked once.
68+
69+
`Emitter::preemptOnce` combines the functionality of `::preempt` and `::once`.
70+
5871
## Creating Your Own Disposables
5972

6073
Disposables are convenient ways to represent a resource you will no longer

spec/emitter-spec.coffee

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,39 @@ describe "Emitter", ->
4444
emitter.emit 'foo', 2
4545
expect(events).toEqual []
4646

47+
it "invokes observers registered with once when the named event is emitted only once unless disposed", ->
48+
emitter = new Emitter
49+
50+
fooEvents = []
51+
barEvents = []
52+
quxEvents = []
53+
54+
sub1 = emitter.once 'foo', (value) -> fooEvents.push(['a', value])
55+
sub2 = emitter.once 'bar', (value) -> barEvents.push(['b', value])
56+
sub3 = emitter.preemptOnce 'bar', (value) -> barEvents.push(['c', value])
57+
sub4 = emitter.once 'qux', (value) -> quxEvents.push(['c', value])
58+
59+
emitter.emit 'foo', 1
60+
emitter.emit 'foo', 2
61+
emitter.emit 'bar', 3
62+
63+
sub1.dispose()
64+
65+
emitter.emit 'foo', 4
66+
emitter.emit 'bar', 5
67+
68+
sub2.dispose()
69+
70+
emitter.emit 'bar', 6
71+
72+
sub4.dispose()
73+
74+
emitter.emit 'qux', 7
75+
76+
expect(fooEvents).toEqual [['a', 1]]
77+
expect(barEvents).toEqual [['c', 3], ['b', 3]]
78+
expect(quxEvents).toEqual []
79+
4780
describe "when a handler throws an exception", ->
4881
describe "when no exception handlers are registered on Emitter", ->
4982
it "throws exceptions as normal, stopping subsequent handlers from firing", ->

src/emitter.coffee

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,27 @@ class Emitter
101101

102102
new Disposable(@off.bind(this, eventName, handler))
103103

104+
# Public: Register the given handler function to be invoked only once whenever
105+
# the next by the given name are emitted via {::emit}. After being invoked,
106+
# will automatically be unsubscribed.
107+
#
108+
# * `eventName` {String} naming the event that you want to invoke the handler
109+
# when emitted.
110+
# * `handler` {Function} to invoke when {::emit} is called with the given
111+
# event name.
112+
#
113+
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
114+
once: (eventName, handler, unshift=false) ->
115+
if typeof handler isnt 'function'
116+
throw new Error("Handler must be a function")
117+
118+
subscription = null
119+
wrappedHandler = (value) ->
120+
handler(value)
121+
subscription.dispose()
122+
123+
subscription = @on(eventName, wrappedHandler, unshift)
124+
104125
# Public: Register the given handler function to be invoked *before* all
105126
# other handlers existing at the time of subscription whenever events by the
106127
# given name are emitted via {::emit}.
@@ -121,6 +142,27 @@ class Emitter
121142
preempt: (eventName, handler) ->
122143
@on(eventName, handler, true)
123144

145+
# Public: Register the given handler function to be invoked *before* all
146+
# other handlers existing at the time of subscription only once the next time
147+
# events by the given name are emitted via {::emit}. After being invoked, it
148+
# will automatically be unsubscribed.
149+
#
150+
# Use this method when you need to be the first to handle a given event. This
151+
# could be required when a data structure in a parent object needs to be
152+
# updated before third-party event handlers registered on a child object via a
153+
# public API are invoked. Your handler could itself be preempted via
154+
# subsequent calls to this method, but this can be controlled by keeping
155+
# methods based on `::preempt` private.
156+
#
157+
# * `eventName` {String} naming the event that you want to invoke the handler
158+
# when emitted.
159+
# * `handler` {Function} to invoke when {::emit} is called with the given
160+
# event name.
161+
#
162+
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
163+
preemptOnce: (eventName, handler) ->
164+
@once(eventName, handler, true)
165+
124166
# Private: Used by the disposable.
125167
off: (eventName, handlerToRemove) ->
126168
return if @disposed

0 commit comments

Comments
 (0)