javascript-state-machine icon indicating copy to clipboard operation
javascript-state-machine copied to clipboard

Throwing on async transition doesn't cancel transition

Open negebauer opened this issue 6 years ago • 2 comments

According to the Asynchronous Transitions doc

Returning a Promise from a lifecycle event will cause the lifecycle for that transition to pause. It can be continued by resolving the promise, or cancelled by rejecting the promise.

Rejecting a promise returned in a lifecycle event should cancel the transition, as it happens when returning false on some lifecycle events like onTransition and onBeforeTransition.

But rejecting the returned promise doesn't have this effect, the transition completes as if the promise was resolved correctly, which can be demonstrated with this code

const StateMachine = require('javascript-state-machine');

const stateMachine = new StateMachine({
  init: 'open',
  transitions: [
    { name: 'close', from: 'open', to: 'closed' },
    { name: 'requireInfo', from: 'open', to: 'requiresInfo' },
  ],
  methods: {
    async onClose() {
      throw new Error('failed close')
    },
    async onRequireInfo() {
      return 'transitioned to require info'
    }
  }
})

async function test() {
  console.log('stateMachine.state:', stateMachine.state)
  try {
    const result = await stateMachine.close()
    console.log('stateMachine.state:', stateMachine.state)
  } catch (error) {
    console.log('error:', error.message)
    console.log('stateMachine.state:', stateMachine.state)
  }
  try {
    const result = await stateMachine.requireInfo()
    console.log('stateMachine.state:', stateMachine.state)
  } catch (error) {

    console.log('error:', error.message)
    console.log('stateMachine.state:', stateMachine.state)
  }
}

test()

Output:

stateMachine.state: open
error: failed close
stateMachine.state: closed
error: transition is invalid in current state
stateMachine.state: closed

The code above should end up transitioning the state from open to requiresInfo, but instead it perform the close transition. There's no actual waiting on the promise, as the catch is only called after all lifecycle events trigger. Changing https://github.com/jakesgordon/javascript-state-machine/blob/0d603577423244228cebcd62e60dbbfff27c6ea3/src/jsm.js#L157 to:

                  .catch((err) => {
                    console.log('called catch')
                    this.failTransit(err);
                  })

And adding a console.log(event) right before https://github.com/jakesgordon/javascript-state-machine/blob/0d603577423244228cebcd62e60dbbfff27c6ea3/src/jsm.js#L148 shows the following output

stateMachine.state: open   # right before calling stateMachine.close()
onBeforeTransition
onBeforeClose
onLeaveState
onLeaveOpen
onTransition
doTransit
doTransit
onEnterState
onEnterClosed
onClosed
onAfterTransition
onAfterClose
onClose
called catch # here the catch is handled, no other lifecycle event should trigger while the promise is still pending
error: failed close
stateMachine.state: closed

When writing this issue I actually noticed that the problem is that there's no waiting on the resolve or reject of a promise, the other lifecycleEvents are triggered no matter what happens with the promise.

negebauer avatar Nov 04 '18 21:11 negebauer

Yes. I confirm this situation. Which is a shame. The only solution would be to return false in on of the above mentioned callbacks.

NelsonFrancisco avatar May 17 '19 10:05 NelsonFrancisco

Will this be fixed ? Is this library dead ?

LeoMartinDev avatar Nov 21 '20 16:11 LeoMartinDev