Skip to content

Commit

Permalink
Merge pull request #93 from vstirbu/feature/noop-stale-transition-cle…
Browse files Browse the repository at this point in the history
…anup

handle errors only thrown by instance
  • Loading branch information
vstirbu committed Feb 13, 2018
2 parents 992c63c + f129f82 commit eefecac
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 11 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# unreleased

* added instanceId to fsm object
* handle only `Invalid event in current state` thrown by own instance

# 0.15.1 / 2017-10-20

* set `final` configuration option type checking to optional
Expand Down
4 changes: 4 additions & 0 deletions lib/fsm-error.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ function FsmError(message, options) {
this.trigger = options.name;
this.current = options.from;

if (options.instanceId) {
this.instanceId = options.instanceId;
}

if (options.pending) {
this.pending = options.pending;
}
Expand Down
44 changes: 34 additions & 10 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @author Vlad Stirbu
* @license MIT
*
* Copyright © 2014-2017
* Copyright © 2014-2018
*/

'use strict';
Expand Down Expand Up @@ -61,8 +61,25 @@ var StateMachine = stampit({
methods: {
emit: _.noop,
error: function(msg, options) {
if (this.target) {
options.instanceId = this.target.instanceId();
}

throw new this.factory.FsmError(msg, options);
},
instanceErrorHandler: function instanceErrorHandler (err, instanceId, action) {
if (err instanceof this.factory.FsmError) {
if (err.message === 'Invalid event in current state') {
if (err.instanceId !== instanceId) {
action();
}
} else {
action();
}
} else {
action();
}
},
canTransition: function canTransition (options) {
var factory = this.factory;
var Type = factory.Type;
Expand Down Expand Up @@ -153,17 +170,20 @@ var StateMachine = stampit({
return function revert (err) {
var factory = this.factory;
var Type = this.factory.Type;
var instanceId = this.target.instanceId();

if (err.message !== 'Invalid event in current state') {
switch (factory.type(options)) {
case Type.INTER:
switch (factory.type(options)) {
case Type.INTER:
this.instanceErrorHandler(err, instanceId, () => {
this.inTransition = false;
break;
case Type.NOOP:
});
break;
case Type.NOOP:
this.instanceErrorHandler(err, instanceId, () => {
delete this.states[this.current].noopTransitions[options.id];
break;
default:
}
});
break;
default:
}

throw err;
Expand Down Expand Up @@ -390,6 +410,7 @@ var StateMachine = stampit({
},
initTarget: function initTarget(target) {
var mixin;
const id = uuid.v4();

if (!_.isObject(target)) {
target = new EventEmitter();
Expand All @@ -410,7 +431,8 @@ var StateMachine = stampit({
cannot: this.cannot.bind(this),
is: this.is.bind(this),
hasState: this.hasState.bind(this),
isFinal: this.isFinal.bind(this)
isFinal: this.isFinal.bind(this),
instanceId: () => id
});

Object.defineProperty(target, 'current', {
Expand All @@ -427,6 +449,8 @@ var StateMachine = stampit({
init: function init (opts, context) {
this.factory = context.stamp;

this.states = {};

var events = this.events;
this.events = {};
_.forEach(events, function (event, name) {
Expand Down
2 changes: 2 additions & 0 deletions test/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ var callbackPrefix = require('./specs/callback-prefix');
var emitter = require('./specs/emitter');
var transitionId = require('./specs/transition-id');
var faultyFsm = require('./specs/faulty-fsm');
var errorIsolation = require('./specs/error-isolation');

var promises = {
Default: defaultPromise,
Expand Down Expand Up @@ -44,5 +45,6 @@ Object.keys(promises).forEach(function (promise) {
emitter(promises[promise]);
transitionId(promises[promise]);
faultyFsm(promises[promise]);
errorIsolation(promises[promise]);
});
});
96 changes: 96 additions & 0 deletions test/specs/error-isolation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
module.exports = function (promise) {
StateMachine.Promise = promise;

describe('FSM error isolation', function () {
it('instance has unique id', function() {
const config = {
initial: 'here',
events: [
{ name: 'stay', from: 'here' },
{ name: 'move', from: 'here', to: 'there' }
]
};

const fsm1 = StateMachine(config);
const fsm2 = StateMachine(config);

expect(fsm1.instanceId()).to.not.equal(fsm2.instanceId);
});

it('`Invalid event in current state` error has originating instance id property', function() {
var fsm = StateMachine({
initial: 'here',
events: [
{ name: 'stay', from: 'here' },
{ name: 'move', from: 'here', to: 'there' },
{ name: 'rest', from: 'there' }
]
});

return fsm.rest().catch(err => {
expect(err.message).to.equal('Invalid event in current state');
expect(err.instanceId).to.equal(fsm.instanceId());
return fsm.move();
});
});

it('handle noop transition error from another fsm instance', function (done) {
var other = StateMachine({
initial: 'one',
events: [
{ name: 'init', from: 'one', to: 'two' },
{ name: 'crash', from: 'two' }
]
});

var fsm = StateMachine({
initial: 'here',
events: [
{ name: 'stay', from: 'here' },
{ name: 'move', from: 'here', to: 'there' }
],
callbacks: {
onstay: function(opts) {
// throws a FsmError from a different fsm instance
return other.crash();
}
}
});

fsm.stay().catch((e) => {
fsm.move()
.then(() => {
done();
});
});
});

it('handle inter transition error from another fsm instance', function () {
var other = StateMachine({
initial: 'one',
events: [
{ name: 'init', from: 'one', to: 'two' },
{ name: 'crash', from: 'two' }
]
});

var fsm = StateMachine({
initial: 'here',
events: [
{ name: 'stay', from: 'here' },
{ name: 'move', from: 'here', to: 'there' },
{ name: 'run', from: 'here', to: 'there' },

],
callbacks: {
onmove: function(opts) {
// throws a FsmError from a different fsm instance
return other.crash();
}
}
});

return fsm.move().catch(() => fsm.run());
});
});
}
2 changes: 1 addition & 1 deletion test/specs/faulty-fsm.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ module.exports = function (promise) {
{ name: 'clear', from: 'yellow', to: 'green' }
]
});

return fsm.warn().catch(err => {
expect(err.message).to.equal('Invalid event in current state');
});
Expand Down

0 comments on commit eefecac

Please sign in to comment.