Skip to content

Commit 724aa0b

Browse files
committed
extend the custom error handler (cfg.error) to support handling the case where a caller provided callback throws an exception
NOTE: I'm not entirely happy with this direction, I'm not sure if this will ever make it onto the master branch - see comments in github issue jakesgordon#3
1 parent 1195fea commit 724aa0b

File tree

5 files changed

+40
-51
lines changed

5 files changed

+40
-51
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Javascript Finite State Machine (v2.0.0)
1+
Javascript Finite State Machine (v2.0.1)
22
========================================
33

44
This standalone javascript micro-framework provides a finite state machine for your pleasure.

RELEASE_NOTES.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
Version 2.0.1 (unreleased)
2+
--------------------------
3+
4+
* extended custom error callback to handle any exceptions caused by caller provided callbacks
5+
* added custom error callback to override exception when an illegal state transition is attempted (thanks to cboone)
6+
* fixed typos (thanks to cboone)
7+
* fixed issue #4 - ensure before/after event hooks are called even if the event doesn't result in a state change
18

29
Version 2.0.0 (August 19th 2011)
310
--------------------------------

state-machine.js

Lines changed: 30 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ StateMachine = {
22

33
//---------------------------------------------------------------------------
44

5-
VERSION: "2.0.0",
5+
VERSION: "2.0.1",
66

77
//---------------------------------------------------------------------------
88

@@ -31,7 +31,7 @@ StateMachine = {
3131

3232
for(var name in map) {
3333
if (map.hasOwnProperty(name))
34-
fsm[name] = StateMachine.buildEvent(name, map[name], cfg.error);
34+
fsm[name] = StateMachine.buildEvent(name, map[name]);
3535
}
3636

3737
for(var name in callbacks) {
@@ -43,6 +43,7 @@ StateMachine = {
4343
fsm.is = function(state) { return this.current == state; };
4444
fsm.can = function(event) { return !!map[event][this.current] && !this.transition; };
4545
fsm.cannot = function(event) { return !this.can(event); };
46+
fsm.error = cfg.error || function(eventName, error) { throw error; };
4647

4748
if (initial && !initial.defer)
4849
fsm[initial.event]();
@@ -53,77 +54,60 @@ StateMachine = {
5354

5455
//===========================================================================
5556

56-
beforeEvent: function(name, from, to, args) {
57-
var func = this['onbefore' + name];
58-
if (func)
59-
return func.apply(this, [name, from, to].concat(args));
60-
},
61-
62-
afterEvent: function(name, from, to, args) {
63-
var func = this['onafter' + name] || this['on' + name];
64-
if (func)
65-
return func.apply(this, [name, from, to].concat(args));
66-
},
67-
68-
leaveState: function(name, from, to, args) {
69-
var func = this['onleave' + from];
70-
if (func)
71-
return func.apply(this, [name, from, to].concat(args));
57+
doCallback: function(fsm, func, name, from, to, args) {
58+
if (func) {
59+
try {
60+
return func.apply(fsm, [name, from, to].concat(args));
61+
}
62+
catch(e) {
63+
fsm.error(name, e);
64+
}
65+
}
7266
},
7367

74-
enterState: function(name, from, to, args) {
75-
var func = this['onenter' + to] || this['on' + to];
76-
if (func)
77-
return func.apply(this, [name, from, to].concat(args));
78-
},
68+
beforeEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onbefore' + name], name, from, to, args); },
69+
afterEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onafter' + name] || fsm['on' + name], name, from, to, args); },
70+
leaveState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onleave' + from], name, from, to, args); },
71+
enterState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onenter' + to] || fsm['on' + to], name, from, to, args); },
72+
changeState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onchangestate'], name, from, to, args); },
7973

80-
changeState: function(name, from, to, args) {
81-
var func = this['onchangestate'];
82-
if (func)
83-
return func.apply(this, [name, from, to].concat(args));
84-
},
8574

86-
buildEvent: function(name, map, errorHandler) {
75+
buildEvent: function(name, map) {
8776
return function() {
8877

8978
if (this.transition)
90-
throw "event " + name + " inappropriate because previous transition did not complete"
79+
return this.error(name, "event " + name + " inappropriate because previous transition did not complete");
9180

92-
if (this.cannot(name)) {
93-
if (errorHandler) {
94-
return errorHandler.call(this, name);
95-
} else {
96-
throw "event " + name + " inappropriate in current state " + this.current;
97-
}
98-
}
81+
if (this.cannot(name))
82+
return this.error(name, "event " + name + " inappropriate in current state " + this.current);
9983

10084
var from = this.current;
10185
var to = map[from];
10286
var args = Array.prototype.slice.call(arguments); // turn arguments into pure array
10387

104-
if (false === StateMachine.beforeEvent.call(this, name, from, to, args))
88+
if (false === StateMachine.beforeEvent(this, name, from, to, args))
10589
return;
10690

10791
if (from !== to) {
10892

109-
var self = this;
93+
var fsm = this;
11094
this.transition = function() { // prepare transition method for use either lower down, or by caller if they want an async transition (indicated by a false return value from leaveState)
111-
self.transition = null; // this method should only ever be called once
112-
self.current = to;
113-
StateMachine.enterState.call( self, name, from, to, args);
114-
StateMachine.changeState.call(self, name, from, to, args);
115-
StateMachine.afterEvent.call( self, name, from, to, args);
95+
fsm.transition = null; // this method should only ever be called once
96+
fsm.current = to;
97+
StateMachine.enterState( fsm, name, from, to, args);
98+
StateMachine.changeState(fsm, name, from, to, args);
99+
StateMachine.afterEvent( fsm, name, from, to, args);
116100
};
117101

118-
if (false !== StateMachine.leaveState.call(this, name, from, to, args)) {
102+
if (false !== StateMachine.leaveState(this, name, from, to, args)) {
119103
if (this.transition) // in case user manually called it but forgot to return false
120104
this.transition();
121105
}
122106

123107
return; // transition method took care of (or, if async, will take care of) the afterEvent, DONT fall through
124108
}
125109

126-
StateMachine.afterEvent.call(this, name, from, to, args); // this is only ever called if there was NO transition (e.g. if from === to)
110+
StateMachine.afterEvent(this, name, from, to, args); // this is only ever called if there was NO transition (e.g. if from === to)
127111

128112
};
129113
}

state-machine.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/test_basics.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,7 @@ test("inappropriate events", function() {
151151
test("inappropriate event handling can be customized", function() {
152152

153153
var fsm = StateMachine.create({
154-
error: function(eventName) {
155-
return 'event ' + eventName + ' inappropriate in current state ' + this.current;
156-
},
154+
error: function(eventName, message) { return message; }, // return error message instead of throwing an exception
157155
initial: 'green',
158156
events: [
159157
{ name: 'warn', from: 'green', to: 'yellow' },

0 commit comments

Comments
 (0)