Each state may define an onExit lambda. The onExit lambda is called every time the FSM leaves the state regardless of what transition caused the FSM to leave the state. The onExit method saves you from having to duplicate common code in for every transition into the state.
..onExit((s, e)=>print('We may be melting'))
By convention the onExit builder should be the first one after the state builder unless there is an onEnter builder in which case it should appear after the onEnter builder.
It is safe to call stateMachine.applyEvent whilst in onExit. The transition will be queued and processed once the current transition has been completed.
When you have a Nested State the onExit action becomes a little more complex. With a Nested State we say that when you enter a state you also enter all ancestor states of that state. In the same way when you leave a state you may leave all of its ancestor states as well as any active child states.
As such we leaving a state we must call onExit for each ancestor upto, but not including, the common ancestor of the old and new state and call onExit for any active child states. (An active state is one that is in the current 'StateOfMind').
As with Class Inheritance we start with the child call its onExit method and the work our way up to the common ancestor.
Concurrent States are less complex than Nested States as each concurrent region has an independent set of states so changing state in one region does not affect the state of the other region. Concurrent States can of course be within Nested States and include Nested states so the normal Nested rules apply.
If the state transitions to a state 'above' the concurrent region (closer to the state tree root) then we may need to call the onExit method of all active states in the concurrent regions. If there are two or more active states in the concurrent regions then we do not define the order in which their onExit methods will be called.