From b5685695025fb01446c4e0c2833afbdcde84501e Mon Sep 17 00:00:00 2001 From: Markus Felten Date: Sat, 26 Dec 2015 15:04:01 +0100 Subject: [PATCH] feat: new method stateTransitionRejection() called when a transition rejects --- StateTransitionMixin.js | 52 ++++++++++++++++++++++++++++++----------- tests/simple.js | 18 +++++++++++++- 2 files changed, 56 insertions(+), 14 deletions(-) diff --git a/StateTransitionMixin.js b/StateTransitionMixin.js index 84add807..94d083e8 100644 --- a/StateTransitionMixin.js +++ b/StateTransitionMixin.js @@ -31,6 +31,20 @@ module.exports.StateTransitionMixin = (superclass, actions, currentState) => cla return Promise.reject(new Error(`Can't ${action.name} ${this} in ${this.state} state`)); } + /** + * Called when the state transtinio implementation Promise rejects. + * Resets the transition + * @return {Promise} rejecting promise + */ + stateTransitionRejection(rejected) { + //this.error(level => `Executing ${this._transition.name} transition leads to ${rejected}`); + this.state = 'failed'; + this._transitionPromise = undefined; + this._transition = undefined; + + return Promise.reject(rejected); + } + /** * To be overwritten * Called when the state changes @@ -71,6 +85,25 @@ function thisResolverPromise() { return Promise.resolve(this); } +/** + * Defines methods to perfom the state transitions. + * States are traversed in the following way: + * current -> during -> final + * If the step is not in one of the transitions current + * states and also not already in the transitions final + * state a rejecting promise will be delivered from the + * generated function. In the 'during' state a function + * named '_' + (sample: '_start()') + * will be called first. + * It is expected that this function delivers a promise. + * Special handling of consequent transitions: + * While in a during state the former delivered primise will be + * delivered again. This enshures that several consequent + * transitions in a row will be fullfiled by the same promise. + * There can only be one transition in place at a given point in time. + * @param {Object} object where we define the metods + * @param {Object} actions object describing the state transitions + */ module.exports.defineActionMethods = function (object, actions) { //console.log(`${JSON.stringify(actions,undefined,1)}`); @@ -87,11 +120,11 @@ module.exports.defineActionMethods = function (object, actions) { Object.defineProperty(object, actionName, { value: function () { if (this._transition) { - if (this.state === this._transition.during) { - return this._transitionPromise; - } - if (this.state === this._transition.target) { - return Promise.resolve(this); + switch (this.state) { + case this._transition.during: + return this._transitionPromise; + case this._transition.target: + return Promise.resolve(this); } } if (action.transitions[this.state]) { @@ -104,14 +137,7 @@ module.exports.defineActionMethods = function (object, actions) { this._transitionPromise = undefined; this._transition = undefined; return this; - }, rejected => { - this.error(level => `Executing ${this._transition.name} transition leads to ${rejected}`); - - this.state = 'failed'; - this._transitionPromise = undefined; - this._transition = undefined; - return Promise.reject(reject); - }); + }, rejected => this.stateTransitionRejection(rejected)); return this._transitionPromise; } else { diff --git a/tests/simple.js b/tests/simple.js index fa6e1b71..dd22f61e 100644 --- a/tests/simple.js +++ b/tests/simple.js @@ -33,11 +33,17 @@ const actions = stm.prepareActions({ class BaseClass {} +var shouldReject = false; + class StatefullClass extends stm.StateTransitionMixin(BaseClass, actions, 'stopped') { _start() { return new Promise((f, r) => { setTimeout(() => { - f(this) + if (shouldReject) { + r(new Error("always reject")); + } else { + f(this); + } }, 10); }); } @@ -101,4 +107,14 @@ describe('states', function () { }); }); + it('handle failure while starting', function (done) { + o1.stop().then(() => { + shouldReject = true; + assert.equal(o1.state, 'stopped'); + o1.start().then(() => {}).catch(e => { + assert.equal(o1.state, 'failed'); + done(); + }); + }); + }); });