Skip to content

Commit 9d71f62

Browse files
committed
removing async option from event definition... mechanism for doing async transition is to return false from an onleavestate handler and be sure to call this.transition() when you are ready to go to the next state.
If you need fine grain control, you can decide at run-time based on the callback's (event,from,to) arguments
1 parent 887ad3f commit 9d71f62

4 files changed

Lines changed: 105 additions & 84 deletions

File tree

README.md

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,9 @@ along with the following members:
8787
Multiple 'from' and 'to' states for a single event
8888
==================================================
8989

90-
If an event is allowed **from** multiple states, and always transitions **to** the same
91-
state, then simply provide an array of states in the `from` attribute of an event.
92-
93-
If an event is allowed **from** multiple states, but should transition **to** a different
90+
If an event is allowed **from** multiple states, and always transitions to the same
91+
state, then simply provide an array of states in the `from` attribute of an event. If
92+
an event is allowed from multiple states, but should transition **to** a different
9493
state depending on the current state, then provide multiple event entries with
9594
the same name:
9695

@@ -145,9 +144,9 @@ In addition, a generic `onchangestate()` callback can be used to call a single f
145144

146145
All callbacks will be passed the same arguments:
147146

148-
* event name
149-
* from state
150-
* to state
147+
* **event** name
148+
* **from** state
149+
* **to** state
151150
* _(followed by any arguments you passed into the original event method)_
152151

153152
Callbacks can be specified when the state machine is first created:
@@ -161,16 +160,16 @@ Callbacks can be specified when the state machine is first created:
161160
{ name: 'clear', from: 'yellow', to: 'green' }
162161
],
163162
callbacks: {
164-
onpanic: function(event, from, to) { alert('panic!'); },
165-
onclear: function(event, from, to) { alert('all clear!'); },
166-
ongreen: function(event, from, to) { document.body.className = 'green'; },
167-
onyellow: function(event, from, to) { document.body.className = 'yellow'; },
168-
onred: function(event, from, to) { document.body.className = 'red'; },
163+
onpanic: function(event, from, to, msg) { alert('panic! ' + msg); },
164+
onclear: function(event, from, to, msg) { alert('thanks to ' + msg); },
165+
ongreen: function(event, from, to) { document.body.className = 'green'; },
166+
onyellow: function(event, from, to) { document.body.className = 'yellow'; },
167+
onred: function(event, from, to) { document.body.className = 'red'; },
169168
}
170169
});
171170

172-
fsm.panic()
173-
fsm.clear()
171+
fsm.panic('killer bees');
172+
fsm.clear('sedatives in the honey pots');
174173
...
175174

176175
Additionally, they can be added and removed from the state machine at any time:
@@ -180,11 +179,6 @@ Additionally, they can be added and removed from the state machine at any time:
180179
fsm.onred = null;
181180
fsm.onchangestate = function(event, from, to) { document.body.className = to; };
182181

183-
Asynchronous State Transitions
184-
==============================
185-
186-
* **TODO**
187-
188182
State Machine Classes
189183
=====================
190184

@@ -219,6 +213,11 @@ instances:
219213

220214
This should be easy to adjust to fit your appropriate mechanism for object construction.
221215

216+
Asynchronous State Transitions
217+
==============================
218+
219+
* **TODO**
220+
222221
Initialization Options
223222
======================
224223

RELEASE_NOTES.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ Version 2.0.0 (unreleased)
33
--------------------------
44

55
* consistent arguments for ALL callbacks, first 3 args are ALWAYS event name, from state and to state, followed by whatever arguments the user passed to the original event method.
6-
* adding support for `async` event transitions (see README)
6+
* adding support for asynchronous state transitions (see README)
77
* added a generic `onchangestate(event,from,to)` callback to detect all state changes with a single function
88

99
Version 1.2.0 (June 21st 2011)
1010
------------------------------
11-
* allows the same event to transition to different states, depending on the current state (see 'Multiple...' sections in README.md)
11+
* allows the same event to transition to different states, depending on the current state (see 'Multiple...' section in README.md)
1212

1313
Version 1.0.0 (June 1st 2011)
1414
-----------------------------

state-machine.js

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,14 +94,13 @@ StateMachine = {
9494
return function() {
9595

9696
if (this.transition)
97-
throw "event " + name + " innapropriate because previous async transition (" + this.transition.event + ") from " + this.transition.from + " to " + this.transition.to + " did not complete"
97+
throw "event " + name + " innapropriate because previous transition (" + this.transition.event + ") from " + this.transition.from + " to " + this.transition.to + " did not complete"
9898

9999
if (this.cannot(name))
100100
throw "event " + name + " innapropriate in current state " + this.current;
101101

102102
var from = this.current;
103103
var to = map[from].to;
104-
var async = map[from].async;
105104
var self = this;
106105
var args = Array.prototype.slice.call(arguments); // turn arguments into pure array
107106

@@ -115,11 +114,10 @@ StateMachine = {
115114
this.transition.from = from;
116115
this.transition.to = to;
117116

118-
if (false === StateMachine.leaveState.call(this, name, from, to, args))
119-
async = true;
120-
121-
if (!async && this.transition) // if not async OR user already called transition method (e.g. explicitly in an onleavestate callback)
122-
this.transition();
117+
if (false !== StateMachine.leaveState.call(this, name, from, to, args)) {
118+
if (this.transition) // in case user manually called it but forgot to return false
119+
this.transition();
120+
}
123121
}
124122

125123
};

test/test_async.js

Lines changed: 80 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,17 @@ test("state transitions", function() {
99
var fsm = StateMachine.create({
1010
initial: 'green',
1111
events: [
12-
{ name: 'warn', from: 'green', to: 'yellow', async: true },
13-
{ name: 'panic', from: 'yellow', to: 'red', async: true },
14-
{ name: 'calm', from: 'red', to: 'yellow', async: true },
15-
{ name: 'clear', from: 'yellow', to: 'green', async: true }
16-
]});
12+
{ name: 'warn', from: 'green', to: 'yellow' },
13+
{ name: 'panic', from: 'yellow', to: 'red' },
14+
{ name: 'calm', from: 'red', to: 'yellow' },
15+
{ name: 'clear', from: 'yellow', to: 'green' }
16+
],
17+
callbacks: {
18+
onleavegreen: function() { return false; },
19+
onleaveyellow: function() { return false; },
20+
onleavered: function() { return false; }
21+
}
22+
});
1723

1824
equals(fsm.current, 'green', "initial state should be green");
1925
fsm.warn(); equals(fsm.current, 'green', "should still be green because we haven't transitioned yet");
@@ -36,11 +42,17 @@ test("state transitions with delays", function() {
3642
var fsm = StateMachine.create({
3743
initial: 'green',
3844
events: [
39-
{ name: 'warn', from: 'green', to: 'yellow', async: true },
40-
{ name: 'panic', from: 'yellow', to: 'red', async: true },
41-
{ name: 'calm', from: 'red', to: 'yellow', async: true },
42-
{ name: 'clear', from: 'yellow', to: 'green', async: true }
43-
]});
45+
{ name: 'warn', from: 'green', to: 'yellow' },
46+
{ name: 'panic', from: 'yellow', to: 'red' },
47+
{ name: 'calm', from: 'red', to: 'yellow' },
48+
{ name: 'clear', from: 'yellow', to: 'green' }
49+
],
50+
callbacks: {
51+
onleavegreen: function() { return false; },
52+
onleaveyellow: function() { return false; },
53+
onleavered: function() { return false; }
54+
}
55+
});
4456

4557
equals(fsm.current, 'green', "initial state should be green");
4658
fsm.warn(); equals(fsm.current, 'green', "should still be green because we haven't transitioned yet");
@@ -65,20 +77,20 @@ test("state transitions with delays", function() {
6577

6678
//-----------------------------------------------------------------------------
6779

68-
test("state transition fired imediately during onleavestate callback", function() {
80+
test("state transition fired during onleavestate callback - immediate", function() {
6981

7082
var fsm = StateMachine.create({
7183
initial: 'green',
7284
events: [
73-
{ name: 'warn', from: 'green', to: 'yellow', async: true },
74-
{ name: 'panic', from: 'yellow', to: 'red', async: true },
75-
{ name: 'calm', from: 'red', to: 'yellow', async: true },
76-
{ name: 'clear', from: 'yellow', to: 'green', async: true }
85+
{ name: 'warn', from: 'green', to: 'yellow' },
86+
{ name: 'panic', from: 'yellow', to: 'red' },
87+
{ name: 'calm', from: 'red', to: 'yellow' },
88+
{ name: 'clear', from: 'yellow', to: 'green' }
7789
],
7890
callbacks: {
79-
onleavegreen: function() { this.transition(); },
80-
onleaveyellow: function() { this.transition(); },
81-
onleavered: function() { this.transition(); }
91+
onleavegreen: function() { this.transition(); return false; },
92+
onleaveyellow: function() { this.transition(); return false; },
93+
onleavered: function() { this.transition(); return false; }
8294
}
8395
});
8496

@@ -93,17 +105,17 @@ test("state transition fired imediately during onleavestate callback", function(
93105

94106
//-----------------------------------------------------------------------------
95107

96-
test("state transition fired during onleavestate callback with delay", function() {
108+
test("state transition fired during onleavestate callback - with delay", function() {
97109

98110
stop(); // doing async stuff - dont run next qunit test until I call start() below
99111

100112
var fsm = StateMachine.create({
101113
initial: 'green',
102114
events: [
103-
{ name: 'panic', from: 'green', to: 'red', async: true }
115+
{ name: 'panic', from: 'green', to: 'red' }
104116
],
105117
callbacks: {
106-
onleavegreen: function() { setTimeout(function() { fsm.transition(); }, 10); },
118+
onleavegreen: function() { setTimeout(function() { fsm.transition(); }, 10); return false; },
107119
onenterred: function() {
108120
equals(fsm.current, 'red', "panic event should transition from green to red");
109121
start();
@@ -118,30 +130,35 @@ test("state transition fired during onleavestate callback with delay", function(
118130

119131
//-----------------------------------------------------------------------------
120132

121-
test("state transition fired during onleavestate callback - with MIXED async and non-async transitions!", function() {
133+
test("state transition fired during onleavestate callback - but forgot to return false!", function() {
122134

123135
var fsm = StateMachine.create({
124136
initial: 'green',
125137
events: [
126-
{ name: 'warn', from: 'green', to: 'yellow', async: true }, // leave green async
127-
{ name: 'panic', from: 'green', to: 'red' }, // leave green non-async
128-
{ name: 'reset', from: ['yellow', 'red'], to: 'green' }
138+
{ name: 'warn', from: 'green', to: 'yellow' },
139+
{ name: 'panic', from: 'yellow', to: 'red' },
140+
{ name: 'calm', from: 'red', to: 'yellow' },
141+
{ name: 'clear', from: 'yellow', to: 'green' }
129142
],
130143
callbacks: {
131-
onleavegreen: function() { this.transition(); }
144+
onleavegreen: function() { this.transition(); /* return false; */ },
145+
onleaveyellow: function() { this.transition(); /* return false; */ },
146+
onleavered: function() { this.transition(); /* return false; */ }
132147
}
133148
});
134149

135-
equals(fsm.current, 'green', "initial state should be green");
150+
equals(fsm.current, 'green', "initial state should be green");
151+
136152
fsm.warn(); equals(fsm.current, 'yellow', "warn event should transition from green to yellow");
137-
fsm.reset(); equals(fsm.current, 'green', "reset event should transition from yellow to green");
138-
fsm.panic(); equals(fsm.current, 'red', "panic event should transition from green to red");
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");
139156

140157
});
141158

142159
//-----------------------------------------------------------------------------
143160

144-
test("state transitions using onleavestate run-time return value instead of design-time async attribute", function() {
161+
test("state transitions sometimes synchronous and sometimes asynchronous", function() {
145162

146163
var fsm = StateMachine.create({
147164
initial: 'green',
@@ -150,7 +167,8 @@ test("state transitions using onleavestate run-time return value instead of desi
150167
{ name: 'panic', from: 'yellow', to: 'red' },
151168
{ name: 'calm', from: 'red', to: 'yellow' },
152169
{ name: 'clear', from: 'yellow', to: 'green' }
153-
]});
170+
]
171+
});
154172

155173
// default behavior is synchronous
156174

@@ -205,18 +223,24 @@ test("state transition fired without completing previous transition", function()
205223
var fsm = StateMachine.create({
206224
initial: 'green',
207225
events: [
208-
{ name: 'warn', from: 'green', to: 'yellow', async: true },
209-
{ name: 'panic', from: 'yellow', to: 'red', async: true },
210-
{ name: 'calm', from: 'red', to: 'yellow', async: true },
211-
{ name: 'clear', from: 'yellow', to: 'green', async: true }
212-
]});
226+
{ name: 'warn', from: 'green', to: 'yellow' },
227+
{ name: 'panic', from: 'yellow', to: 'red' },
228+
{ name: 'calm', from: 'red', to: 'yellow' },
229+
{ name: 'clear', from: 'yellow', to: 'green' }
230+
],
231+
callbacks: {
232+
onleavegreen: function() { return false; },
233+
onleaveyellow: function() { return false; },
234+
onleavered: function() { return false; }
235+
}
236+
});
213237

214238
equals(fsm.current, 'green', "initial state should be green");
215239
fsm.warn(); equals(fsm.current, 'green', "should still be green because we haven't transitioned yet");
216240
fsm.transition(); equals(fsm.current, 'yellow', "warn event should transition from green to yellow");
217241
fsm.panic(); equals(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet");
218242

219-
raises(fsm.calm.bind(fsm), /event calm innapropriate because previous async transition \(panic\) from yellow to red did not complete/);
243+
raises(fsm.calm.bind(fsm), /event calm innapropriate because previous transition \(panic\) from yellow to red did not complete/);
220244

221245
});
222246

@@ -229,29 +253,29 @@ test("callbacks are ordered correctly", function() {
229253
var fsm = StateMachine.create({
230254
initial: 'green',
231255
events: [
232-
{ name: 'warn', from: 'green', to: 'yellow', async: true },
233-
{ name: 'panic', from: 'yellow', to: 'red', async: true },
234-
{ name: 'calm', from: 'red', to: 'yellow', async: true },
235-
{ name: 'clear', from: 'yellow', to: 'green', async: true },
256+
{ name: 'warn', from: 'green', to: 'yellow' },
257+
{ name: 'panic', from: 'yellow', to: 'red' },
258+
{ name: 'calm', from: 'red', to: 'yellow' },
259+
{ name: 'clear', from: 'yellow', to: 'green' },
236260
],
237261
callbacks: {
238262
onchangestate: function(event,from,to) { called.push('onchange from ' + from + ' to ' + to); },
239263

240-
onentergreen: function() { called.push('onentergreen'); },
241-
onleavegreen: function() { called.push('onleavegreen'); },
242-
onenteryellow: function() { called.push('onenteryellow'); },
243-
onleaveyellow: function() { called.push('onleaveyellow'); },
244-
onenterred: function() { called.push('onenterred'); },
245-
onleavered: function() { called.push('onleavered'); },
246-
247-
onbeforewarn: function() { called.push('onbeforewarn'); },
248-
onafterwarn: function() { called.push('onafterwarn'); },
249-
onbeforepanic: function() { called.push('onbeforepanic'); },
250-
onafterpanic: function() { called.push('onafterpanic'); },
251-
onbeforecalm: function() { called.push('onbeforecalm'); },
252-
onaftercalm: function() { called.push('onaftercalm'); },
253-
onbeforeclear: function() { called.push('onbeforeclear'); },
254-
onafterclear: function() { called.push('onafterclear'); }
264+
onentergreen: function() { called.push('onentergreen'); },
265+
onenteryellow: function() { called.push('onenteryellow'); },
266+
onenterred: function() { called.push('onenterred'); },
267+
onleavegreen: function() { called.push('onleavegreen'); return false; },
268+
onleaveyellow: function() { called.push('onleaveyellow'); return false; },
269+
onleavered: function() { called.push('onleavered'); return false; },
270+
271+
onbeforewarn: function() { called.push('onbeforewarn'); },
272+
onbeforepanic: function() { called.push('onbeforepanic'); },
273+
onbeforecalm: function() { called.push('onbeforecalm'); },
274+
onbeforeclear: function() { called.push('onbeforeclear'); },
275+
onafterwarn: function() { called.push('onafterwarn'); },
276+
onafterpanic: function() { called.push('onafterpanic'); },
277+
onaftercalm: function() { called.push('onaftercalm'); },
278+
onafterclear: function() { called.push('onafterclear'); }
255279
}
256280
});
257281

0 commit comments

Comments
 (0)