A state machine implementation featuring:
-
on<state>life-cycle events, allowing the triggering of further (internal) events during the callback. -
asyncevent functions that can be awaited. Depending on the implemented logic, multiple state changes can be awaited. -
Generic and awaitable
waitUntilStateEnters(<state>)andwaitUntilStateLeaves(<state>)functions providing full flexibility to state machine clients business logic.
Define the transition table as a json object,
const transitionTable = {
initial: 'disconnected',
transitions: [
{ ev: 'connect', from: 'disconnected', to: 'connecting' },
{ ev: '_connectDone', from: 'connecting', to: 'connected' },
{ ev: 'disconnect', from: 'connected', to: 'disconnecting' },
{ ev: '_disconnectDone', from: 'disconnecting', to: 'disconnected' }
]
}then apply this logic to your object:
const StateMachine = require('fsm-async')
class MyClient extends StateMachine {
constructor () {
const transitionTable = {
initial: 'disconnected',
transitions: [
{ ev: 'connect', from: 'disconnected', to: 'connecting' },
{ ev: '_connectDone', from: 'connecting', to: 'connected' },
{ ev: 'disconnect', from: 'connected', to: 'disconnecting' },
{ ev: '_disconnectDone', from: 'disconnecting', to: 'disconnected' }
]
}
super(transitionTable)
}
}This injects the events as proper callable functions to your instance, hence you can write:
myClient = new MyClient()
myClient.connect()In the body of your class you can define life-cycle functions on<event> and
on<state>, which are automatically called and can be used to trigger
further events:
const StateMachine = require('fsm-async')
class MyClient extends StateMachine {
constructor () {
const transitionTable = {
initial: 'disconnected',
transitions: [
{ ev: 'connect', from: 'disconnected', to: 'connecting' },
{ ev: '_connectDone', from: 'connecting', to: 'connected' },
{ ev: 'disconnect', from: 'connected', to: 'disconnecting' },
{ ev: '_disconnectDone', from: 'disconnecting', to: 'disconnected' }
]
}
super(transitionTable)
}
// Use async here to be able to await internally
async onConnecting () {
// Simulate connection establishment
await new Promise(resolve => setTimeout(resolve, 1000))
// Internally trigger an event bringing the machine to connected state
this._connectDone()
}
async onDisconnecting () {
// Simulate disconnection
await new Promise(resolve => setTimeout(resolve, 1000))
// Internally trigger an event bringing the machine to disconnected state
this._disconnectDone()
}
}Now, outer code can await the connect() of your client and/or use other
utility functions injected by the StateMachine. The utility functions are:
getState()returns current statewaitUntilStateEnters(<state>)waits until a given state is enteredwaitUntilStateLeaves(<state>)waits until a given state is leftonStateChange(<callback(state)>)notifies about state changesonInvalidTransition(<callback(event, state)>)notifies about invalid transitions
The StateMachine class at the same time is an event emitter. Hence,
stateMachine.on('state', <callback(state)>)
stateMachine.on('invalidTransition', <callback(event, state)>)
is also possible.
Please see the provided example code (examples folder) for more details and
usage patterns.
You can run the example code via:
npm run example