Skip to content
This repository was archived by the owner on Dec 15, 2022. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions spec/emitter-spec.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -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", ->
Expand Down
42 changes: 42 additions & 0 deletions src/emitter.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -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}.
Expand All @@ -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
Expand Down