Skip to content

Commit a4dbacd

Browse files
committed
allow the choice between sync and async transition to be made at run-time if desired by using onleavestate return value (false) instead of an explicit design-time async attribute on the event (although still support both methods)
1 parent b183d32 commit a4dbacd

2 files changed

Lines changed: 76 additions & 16 deletions

File tree

state-machine.js

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -48,32 +48,32 @@ StateMachine = {
4848

4949
beforeEvent: function(name, args) {
5050
var func = this['onbefore' + name];
51-
if (func && (false === func.apply(this, args)))
52-
return false;
51+
if (func)
52+
return func.apply(this, args);
53+
},
54+
55+
afterEvent: function(name, args) {
56+
var func = this['onafter' + name] || this['on' + name];
57+
if (func)
58+
return func.apply(this, args);
5359
},
5460

55-
exitState: function(from, args) {
61+
leaveState: function(from, args) {
5662
var func = this['onleave' + from];
5763
if (func)
58-
func.apply(this, args);
64+
return func.apply(this, args);
5965
},
6066

6167
enterState: function(to, args) {
6268
var func = this['onenter' + to] || this['on' + to];
6369
if (func)
64-
func.apply(this, args);
70+
return func.apply(this, args);
6571
},
6672

6773
changeState: function(from, to, args) {
6874
var func = this['onchangestate'];
6975
if (func)
70-
func.apply(this, [from,to].concat(args));
71-
},
72-
73-
afterEvent: function(name, args) {
74-
var func = this['onafter' + name] || this['on' + name];
75-
if (func)
76-
func.apply(this, args);
76+
return func.apply(this, [from,to].concat(args));
7777
},
7878

7979
transition: function(name, from, to, args) {
@@ -96,16 +96,21 @@ StateMachine = {
9696
var to = map[from].to;
9797
var async = map[from].async;
9898
var self = this;
99-
var args = Array.prototype.slice.call(arguments);
99+
var args = Array.prototype.slice.call(arguments); // turn arguments into pure array
100100

101101
if (this.current != to) {
102-
if (StateMachine.beforeEvent.call(this, name, args) === false)
102+
103+
if (false === StateMachine.beforeEvent.call(this, name, args))
103104
return;
105+
104106
this.transition = function() { StateMachine.transition.call(self, name, from, to, args); self.transition = null; };
105107
this.transition.from = from;
106108
this.transition.to = to;
107-
StateMachine.exitState.call(this, this.current, arguments);
108-
if (!async && this.transition) // if not async OR user already called transition method (e.g. in an onleavestate hook)
109+
110+
if (false === StateMachine.leaveState.call(this, this.current, args))
111+
async = true;
112+
113+
if (!async && this.transition) // if not async OR user already called transition method (e.g. explicitly in an onleavestate hook)
109114
this.transition();
110115
}
111116

test/test_async.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,61 @@ test("state transition fired during onleavestate hook - with MIXED async and non
135135

136136
//-----------------------------------------------------------------------------
137137

138+
test("state transitions using onleavestate run-time return value instead of design-time async attribute", function() {
139+
140+
var fsm = StateMachine.create({
141+
initial: 'green',
142+
events: [
143+
{ name: 'warn', from: 'green', to: 'yellow' },
144+
{ name: 'panic', from: 'yellow', to: 'red' },
145+
{ name: 'calm', from: 'red', to: 'yellow' },
146+
{ name: 'clear', from: 'yellow', to: 'green' }
147+
]});
148+
149+
// default behavior is synchronous
150+
151+
equals(fsm.current, 'green', "initial state should be green");
152+
fsm.warn(); equals(fsm.current, 'yellow', "warn event should transition from green to yellow");
153+
fsm.panic(); equals(fsm.current, 'red', "panic event should transition from yellow to red");
154+
fsm.calm(); equals(fsm.current, 'yellow', "calm event should transition from red to yellow");
155+
fsm.clear(); equals(fsm.current, 'green', "clear event should transition from yellow to green");
156+
157+
// but add hooks that return false and it magically becomes asynchronous
158+
159+
fsm.onleavegreen = function() { return false; }
160+
fsm.onleaveyellow = function() { return false; }
161+
fsm.onleavered = function() { return false; }
162+
163+
equals(fsm.current, 'green', "initial state should be green");
164+
fsm.warn(); equals(fsm.current, 'green', "should still be green because we haven't transitioned yet");
165+
fsm.transition(); equals(fsm.current, 'yellow', "warn event should transition from green to yellow");
166+
fsm.panic(); equals(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet");
167+
fsm.transition(); equals(fsm.current, 'red', "panic event should transition from yellow to red");
168+
fsm.calm(); equals(fsm.current, 'red', "should still be red because we haven't transitioned yet");
169+
fsm.transition(); equals(fsm.current, 'yellow', "calm event should transition from red to yellow");
170+
fsm.clear(); equals(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet");
171+
fsm.transition(); equals(fsm.current, 'green', "clear event should transition from yellow to green");
172+
173+
// this allows you to make on-the-fly decisions about whether async or not ...
174+
175+
fsm.onleavegreen = function(async) {
176+
if (async) {
177+
setTimeout(function() { fsm.transition() }, 10);
178+
return false;
179+
}
180+
}
181+
fsm.onleaveyellow = fsm.onleavered = null;
182+
183+
fsm.warn(false); equals(fsm.current, 'yellow', "expected synchronous transition from green to yellow");
184+
fsm.clear(); equals(fsm.current, 'green', "clear event should transition from yellow to green");
185+
fsm.warn(true); equals(fsm.current, 'green', "should still be green because we haven't transitioned yet");
186+
fsm.transition(); equals(fsm.current, 'yellow', "warn event should transition from green to yellow");
187+
188+
});
189+
190+
//-----------------------------------------------------------------------------
191+
192+
138193
test("state transition fired without completing previous transition", function() {
139194

140195
var fsm = StateMachine.create({

0 commit comments

Comments
 (0)