Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

expanded and improved error handling.

catches errors thrown in action functions. asumes end and fatal states (success and fail terminal states)

this.callback('eventname') makes callback for async which automaticially handles error in first position
  • Loading branch information...
commit 085efea575ae5d3267c66486fb6d15f4de083668 1 parent 16347e9
@dominictarr authored
View
2  .gitignore
@@ -0,0 +1,2 @@
+node_modules
+node_modules/*
View
82 fsm.js
@@ -1,4 +1,6 @@
+var curry = require('curry')
+
module.exports = FSM
function first(obj){
@@ -10,8 +12,30 @@ function first(obj){
function FSM (schema){
if(!(this instanceof FSM)) return new FSM (schema)
- var state = first(schema)
+ var state = first(schema) //name it start, OR ELSE
+ , self = this
+ , callback
+
+ this.transitions = []
+
+ if(!schema.end)
+ schema.end = {
+ _in: function (){callback.apply(null,[null].concat([].slice.call(arguments)))}
+ }
+
+ if(!schema.fatal)
+ schema.fatal = {
+ _in: function (err){
+ console.log('FATAL ERROR')
+ if('function' !== typeof callback)
+ throw arguments
+ callback.apply(null,[].slice.call(arguments))
+ }
+ }
+ this.local = {}
+ this.save = {}
+
this.getState = function (){
return state
}
@@ -48,29 +72,57 @@ function FSM (schema){
var isArray = Array.isArray
function applyAll(list,self,args){
- if('function' == typeof list)
- return list.apply(self,args)
- list.forEach(function (e){
- e.apply(self,args)
- })
+ try {
+ if('function' == typeof list)
+ return list.apply(self,args)
+ list.forEach(function (e){
+ e.apply(self,args)
+ })
+ }catch (err){
+ if(state == 'fatal' || state == 'end')//once we're out of the FSM errors are not our business any more
+ throw err
+ self.event('error', [err].concat(args)) //follow error transition action throws
+ }
+ }
+ this.callback = function (eventname){ //add options to apply timeout
+ return function () {
+ var args = [].slice.call(arguments)
+ self.event(args[0] ? 'error' : eventname, args)
+ }
}
-
this.event = function (e,args){
var oldState = state
+ , trans = schema[state][e] || (e === 'error' ? 'fatal' : null)
+
args = args || []
- var trans = schema[state][e]
- if('string' === typeof trans && isState(trans)){
- state = trans
- } else if (isArray(trans) && isState(trans[0])){
- console.log('**********', trans)
- state = trans[0]
- applyAll(trans.splice(1),this,args)
- }
+
+ if('string' === typeof trans && isState(trans)){
+ console.log(state,'->', trans)
+ state = trans
+ this.transitions .push(e)
+ } else if (isArray(trans) && isState(trans[0])){
+ console.log('**********', trans)
+ state = trans[0]
+ this.transitions .push(e)
+ applyAll(trans.splice(1),this,args)
+ }
console.log( e,':',oldState,'->',state)
if(schema[state]._in)
applyAll(schema[state]._in,this,args)
+
return this
}
+
+ this.getEvents().forEach(function (e){
+ self[e] = function (){self.event(e,[].slice.call(arguments))}
+ })
+
+ this.call = function (){
+ args = [].slice.call(arguments)
+ if('function' === typeof args[args.length - 1])
+ callback = args.pop()
+ applyAll(schema.start._in,this,args)
+ }
}
View
4 package.json
@@ -15,7 +15,9 @@
"engines": {
"node": ">= 0.2.0"
},
- "dependencies": {},
+ "dependencies": {
+ "curry" : "0.0"
+ },
"devDependencies": {
"it-is" : "0.0"
}
View
88 test/fsm.asynct.js
@@ -0,0 +1,88 @@
+
+
+var FSM = require('../fsm')
+ , it = require('it-is')
+
+/*
+turn fsm into a function
+
+then, provide a mock interface
+
+construct tests for every state and every transition, using fixtures.
+
+run the tests,
+
+FSM will tell you if you have coverage for every path.
+
+all transitions defined should be tested.
+must always be possible to get from start state to end states
+
+*/
+
+exports ['FSM.call turns fsm into a function'] = function (test){
+
+ var fsm = new FSM({
+ start: {
+ _in: function (){
+ it(arguments).deepEqual((function(){return arguments})(1,2,3))
+ test.done()
+ }
+ }
+ }).call(1,2,3)
+}
+
+exports ['start fsm rolling and it will callback when it gets to end'] = function (test){
+
+ var fsm = new FSM({
+ start: {
+ _in: function (){
+ it(arguments).deepEqual((function(){return arguments})(1,2,3))
+ setTimeout(this.done,10)
+ },
+ done: 'end'
+ }
+ }).call(1,2,3, function (err){
+ it(err).equal(null)
+ test.done()
+ })
+}
+
+exports ['if an event function throws, follow error transition'] = function (test){
+
+ var err = new Error('a error occured in the event action')
+ , fsm =
+ new FSM({
+ start: {
+ _in: function () {
+ throw err
+ },
+ done: 'end'
+ }
+ }).call(1,2,3, function (_err){
+ console.log('error', _err)
+ it(_err).equal(err)
+ test.done()
+ })
+}
+
+exports ['callback function which auto handles errors'] = function (test){
+
+ new FSM({
+ start: {
+ _in: function (){
+ setTimeout(this.callback('next'),10)
+ },
+ next: 'middle'
+ },
+ middle: {
+ _in: function (){
+ console.log('MIDDLE')
+ setTimeout(this.callback('next'),10)
+ },
+ next: 'end'
+ }
+ }).call(1,2,3, function (err){
+ it(err).equal(null)
+ test.done()
+ })
+}
View
18 test/fsm.synct.js
@@ -21,7 +21,7 @@ exports ['basic'] = function (){
}
})
- it(fsm.getStates()).deepEqual(['s1','s2'])
+ it(fsm.getStates()).has(['s1','s2'])//end and fatal states will be created automaticially
it(fsm.getEvents()).deepEqual(['e1','e2'])
it(s2).equal(false)
@@ -91,6 +91,20 @@ exports ['sequence'] = function (){
}]
}
})
-
it(fsm.sequence('e1 e2 e1 e2'.split(' ')).getState()).equal('s1')
}
+
+exports ['create functions for each event'] = function (){
+
+ var fsm = new FSM({
+ s1: {
+ e1: 's2', //transition to state 2
+ e2: 's1' //stay in this state
+ }
+ , s2: {
+ e1: ['s1', function (){}]
+ }
+ })
+
+ it([fsm.e1,fsm.e2]).every(it.function())
+}
Please sign in to comment.
Something went wrong with that request. Please try again.