Event System
AFSM extends eventemitter3, so state changes are dispatched as events.
stateChanged event
Every state change triggers FSM.STATECHANGED (i.e. 'stateChanged'):
obj.on(FSM.STATECHANGED, (newState, oldState, err?) => { console.log(`${oldState} → ${newState}`)})Parameters:
newState: State— new state (string orMiddleState)oldState: State— old stateerr?: any— error carried on failure rollback
Per-state events
Entering a stable state dispatches an event with that state name:
obj.on('connected', (oldState) => { console.log(`connected, old state was ${oldState}`)})Intermediate state events
Entering an intermediate state dispatches ${action}ing:
obj.on('connecting', () => { console.log('connecting...')})This lets you listen to “start connecting” and “connection done” separately:
obj.on('connecting', () => showSpinner())obj.on('connected', () => hideSpinner())// on failure, 'connected' won't fireOne-time listeners
eventemitter3 provides once:
obj.once('connected', () => { console.log('first connection succeeded')})Removing listeners
const cb = (newState: State) => console.log(newState)obj.on(FSM.STATECHANGED, cb)obj.off(FSM.STATECHANGED, cb)// or remove allobj.removeAllListeners()Custom events
Your methods can emit any event via emit (inherited from EventEmitter):
class Timer extends FSM { @ChangeState(FSM.ON, FSM.OFF) async timeout() { this.emit('timeout', Date.now()) }}t.on('timeout' as any, (ts: number) => console.log('fired at', ts))Typed events via generics
interface PlayerEvents { play: [url: string] stop: [] stateChanged: [State, State, any]}
class Player extends FSM<PlayerEvents> { @ChangeState('idle', 'playing') async play(url: string) { this.emit('play', url) // type-safe }}Next steps
- Error Handling —
FSMErrorin depth - API: FSM class — full EventEmitter interface