| Conditions | 2 |
| Paths | 1 |
| Total Lines | 338 |
| Lines | 0 |
| Ratio | 0 % |
| Changes | 1 | ||
| Bugs | 0 | Features | 0 |
Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.
For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.
Commonly applied refactorings include:
If many parameters/temporary variables are present:
| 1 | var SDK = require('@ama-team/voxengine-sdk') |
||
| 20 | function StateMachine (executor, scenario, loggerOpts) { |
||
| 21 | // this list contains all unfinished transitions; as soon as transition |
||
| 22 | // has completed or aborted, it is removed from this list |
||
| 23 | var transitions = [] |
||
| 24 | var transition = null |
||
| 25 | var states = scenario.states |
||
| 26 | var errorHandler = scenario.onError |
||
| 27 | var state = null |
||
| 28 | var stage = Stage.Idle |
||
| 29 | var termination = new Future() |
||
| 30 | var logger = Slf4j.factory(loggerOpts, 'ama-team.vsf.execution.state-machine') |
||
| 31 | /** |
||
| 32 | * @type {TTransitionHistoryEntry[]} |
||
| 33 | */ |
||
| 34 | var history = [] |
||
| 35 | var entrypoint = Object.keys(states).reduce(function (state, key) { |
||
| 36 | return state || (states[key].entrypoint ? states[key] : null) |
||
| 37 | }, null) |
||
| 38 | if (!entrypoint) { |
||
| 39 | throw new ScenarioError('No entrypoint state has been defined') |
||
| 40 | } |
||
| 41 | |||
| 42 | /** |
||
| 43 | * @param {StateMachine.Stage} next |
||
| 44 | */ |
||
| 45 | function setStage (next) { |
||
| 46 | logger.debug('Changing status from {} to {}', stage.id, next.id) |
||
| 47 | stage = next |
||
| 48 | } |
||
| 49 | |||
| 50 | /** |
||
| 51 | * Saves current transition status |
||
| 52 | * |
||
| 53 | * @param {Transition} t8n |
||
| 54 | * @param {*} [value] Transition value (if it has finished) |
||
| 55 | */ |
||
| 56 | function snapshot (t8n, value) { |
||
| 57 | var origin = t8n.getOrigin() |
||
| 58 | var entry = { |
||
| 59 | origin: (origin && origin.id) || null, |
||
| 60 | target: t8n.getTarget().id, |
||
| 61 | hints: t8n.getHints(), |
||
| 62 | status: t8n.getStatus(), |
||
| 63 | value: value || null |
||
| 64 | } |
||
| 65 | history.push(entry) |
||
| 66 | while (history.length > 100) { |
||
| 67 | history.shift() |
||
| 68 | } |
||
| 69 | } |
||
| 70 | |||
| 71 | function requireState (id) { |
||
| 72 | var state = states[id] |
||
| 73 | if (state) { |
||
| 74 | return state |
||
| 75 | } |
||
| 76 | var msg = 'Could not find requested state ' + id + ' in provided scenario' |
||
| 77 | throw new ScenarioError(msg) |
||
| 78 | } |
||
| 79 | |||
| 80 | /** |
||
| 81 | * Triggers transition to specified state |
||
| 82 | * |
||
| 83 | * @param {TState} target |
||
| 84 | * @param {THints} hints |
||
| 85 | */ |
||
| 86 | function transitionTo (target, hints) { |
||
| 87 | if (stage.terminal) { |
||
| 88 | var message = 'Can\'t launch new transition from stage ' + stage.id |
||
| 89 | throw new Errors.IllegalStateError(message) |
||
| 90 | } |
||
| 91 | var options = { |
||
| 92 | logger: loggerOpts, |
||
| 93 | origin: state, |
||
| 94 | target: target, |
||
| 95 | hints: hints || {}, |
||
| 96 | executor: executor |
||
| 97 | } |
||
| 98 | return launch(new Transition(options)) |
||
| 99 | } |
||
| 100 | |||
| 101 | /** |
||
| 102 | * Aborts current transition (if any) |
||
| 103 | */ |
||
| 104 | function abort () { |
||
| 105 | if (!transition) { |
||
| 106 | return |
||
| 107 | } |
||
| 108 | logger.debug('Aborting current transition {}', transition) |
||
| 109 | transition.abort() |
||
| 110 | snapshot(transition) |
||
| 111 | transition = null |
||
| 112 | } |
||
| 113 | |||
| 114 | /** |
||
| 115 | * Launches provided transition, aborting running one (if any) and specifying |
||
| 116 | * any necessary hooks |
||
| 117 | * |
||
| 118 | * @param {Transition} t8n |
||
| 119 | * |
||
| 120 | * @return {Thenable} |
||
| 121 | */ |
||
| 122 | function launch (t8n) { |
||
| 123 | abort() |
||
| 124 | transition = t8n |
||
| 125 | transitions.push(t8n) |
||
| 126 | snapshot(t8n) |
||
| 127 | setStage(Stage.Running) |
||
| 128 | var promise = t8n |
||
| 129 | .run() |
||
| 130 | .then(null, function (error) { |
||
| 131 | logger.error('{} run has rejected', t8n.toString()) |
||
| 132 | return { |
||
| 133 | value: error, |
||
| 134 | status: Transition.Stage.Tripped, |
||
| 135 | duration: (new Date()).getTime() - t8n.getLaunchedAt().getTime() |
||
| 136 | } |
||
| 137 | }) |
||
| 138 | promise.then(processResult.bind(null, t8n)) |
||
| 139 | return promise |
||
| 140 | } |
||
| 141 | |||
| 142 | /** |
||
| 143 | * Processes current transition result. |
||
| 144 | * |
||
| 145 | * @param {Transition} t8n |
||
| 146 | * @param {TTransitionResult} result |
||
| 147 | */ |
||
| 148 | function processResult (t8n, result) { |
||
| 149 | logger.debug('{} has finished in {} ms', t8n.toString(), result.duration) |
||
| 150 | setStage(Stage.Idle) |
||
| 151 | snapshot(t8n, result.value) |
||
| 152 | var index = transitions.indexOf(t8n) |
||
| 153 | transitions = index > -1 ? transitions.splice(index, 1) : transitions |
||
| 154 | var current = t8n === transition |
||
| 155 | transition = current ? null : transition |
||
| 156 | if (!current) { |
||
| 157 | return result |
||
| 158 | } |
||
| 159 | var error = result.value |
||
| 160 | if (result.status.successful) { |
||
| 161 | try { |
||
| 162 | return processSuccess(t8n, result.value) |
||
| 163 | } catch (e) { |
||
| 164 | error = e |
||
| 165 | } |
||
| 166 | } |
||
| 167 | processError(t8n, error) |
||
| 168 | } |
||
| 169 | |||
| 170 | /** |
||
| 171 | * Processes transition success. |
||
| 172 | * |
||
| 173 | * @param {Transition} t8n |
||
| 174 | * @param {*} value |
||
| 175 | */ |
||
| 176 | function processSuccess (t8n, value) { |
||
| 177 | logger.debug('{} has resolved with {}, processing', t8n.toString(), value) |
||
| 178 | value = Normalizer.transition(value) |
||
| 179 | var destination = t8n.getTarget() |
||
| 180 | if (value.transitionedTo) { |
||
| 181 | destination = states[value.transitionedTo] |
||
| 182 | if (!destination) { |
||
| 183 | var message = t8n + ' reported transition to state ' + |
||
| 184 | value.transitionedTo + ' which is not present in scenario states' |
||
| 185 | throw new ScenarioError(message) |
||
| 186 | } |
||
| 187 | } |
||
| 188 | logger.debug('Transitioned to {}', destination.id) |
||
| 189 | state = destination |
||
| 190 | if (state.terminal) { |
||
| 191 | logger.info('State `{}` is terminal, halting any further processing', |
||
| 192 | state.id) |
||
| 193 | terminate(OperationStatus.Finished, value) |
||
| 194 | return |
||
| 195 | } |
||
| 196 | if (!processTrigger(value.trigger || destination.triggers)) { |
||
| 197 | logger.info('{} didn\'t trigger transition to next state, doing nothing', |
||
| 198 | t8n) |
||
| 199 | } |
||
| 200 | } |
||
| 201 | |||
| 202 | function processTrigger (trigger) { |
||
| 203 | logger.trace('Processing trigger {}', trigger) |
||
| 204 | trigger = Normalizer.stateTrigger(trigger) |
||
| 205 | if (!trigger || !trigger.id) { |
||
| 206 | logger.trace('Trigger did not specify transition to next state') |
||
| 207 | return false |
||
| 208 | } |
||
| 209 | var hints = trigger && trigger.hints |
||
| 210 | hints = Objects.isFunction(hints) ? executor.execute(hints) : hints |
||
| 211 | transitionTo(requireState(trigger.id), hints) |
||
| 212 | return true |
||
| 213 | } |
||
| 214 | |||
| 215 | /** |
||
| 216 | * Process transition error |
||
| 217 | * |
||
| 218 | * @param {Transition} t8n |
||
| 219 | * @param {Error|*} error |
||
| 220 | */ |
||
| 221 | function processError (t8n, error) { |
||
| 222 | setStage(Stage.ErrorHandling) |
||
| 223 | if (error instanceof InternalError) { |
||
| 224 | logger.error('Framework has thrown an error during {}, halting', |
||
| 225 | t8n.toString()) |
||
| 226 | return terminate(OperationStatus.Tripped, error) |
||
| 227 | } |
||
| 228 | logger.error('{} has finished with error, running error handler', |
||
| 229 | t8n.toString()) |
||
| 230 | var originId = (t8n.getOrigin() && t8n.getOrigin().id) || null |
||
| 231 | var args = [error, originId, t8n.getTarget().id, t8n.getHints()] |
||
| 232 | executor |
||
| 233 | .runHandler(errorHandler, args) |
||
| 234 | .then(function (value) { |
||
| 235 | return processTrigger(value && value.trigger) |
||
| 236 | }, function (e) { |
||
| 237 | logger.error('Outrageous! Error handler has thrown an error ' + |
||
| 238 | 'itself: {}', e) |
||
| 239 | }) |
||
| 240 | .then(function (success) { |
||
| 241 | if (success) { |
||
| 242 | logger.notice('Error handler has rescued from {} error', t8n.toString()) |
||
| 243 | return |
||
| 244 | } |
||
| 245 | terminate(OperationStatus.Failed, error) |
||
| 246 | }) |
||
| 247 | } |
||
| 248 | |||
| 249 | /** |
||
| 250 | * Terminates all processing, forbidding new transitions and resolving |
||
| 251 | * termination as soon as all transitions will finish |
||
| 252 | * |
||
| 253 | * @param {OperationStatus} status |
||
| 254 | * @param {*} [value] |
||
| 255 | */ |
||
| 256 | function terminate (status, value) { |
||
| 257 | setStage(Stage.Terminating) |
||
| 258 | logger.debug('Waiting for {} transitions to finish', transitions.length) |
||
| 259 | var promises = transitions.map(function (transition) { |
||
| 260 | var silencer = function () {} |
||
| 261 | return transition.getCompletion().then(silencer, silencer) |
||
| 262 | }) |
||
| 263 | Promise.all(promises).then(function () { |
||
| 264 | setStage(Stage.Terminated) |
||
| 265 | termination.resolve({ |
||
| 266 | status: status, |
||
| 267 | value: value || null |
||
| 268 | }) |
||
| 269 | }) |
||
| 270 | } |
||
| 271 | |||
| 272 | this.terminate = function () { |
||
| 273 | if (stage.terminal) { |
||
| 274 | var message = 'Can not terminate non-active state machine' |
||
| 275 | throw new Errors.IllegalStateError(message) |
||
| 276 | } |
||
| 277 | abort() |
||
| 278 | terminate(OperationStatus.Aborted, null) |
||
| 279 | return termination |
||
| 280 | } |
||
| 281 | |||
| 282 | /** |
||
| 283 | * Returns current states |
||
| 284 | * |
||
| 285 | * @return {TState} |
||
| 286 | */ |
||
| 287 | this.getState = function () { |
||
| 288 | return state |
||
| 289 | } |
||
| 290 | |||
| 291 | /** |
||
| 292 | * |
||
| 293 | * @return {Transition[]} |
||
| 294 | */ |
||
| 295 | this.getTransitions = function () { |
||
| 296 | return transitions.slice() |
||
| 297 | } |
||
| 298 | |||
| 299 | /** |
||
| 300 | * |
||
| 301 | * @return {Transition} |
||
| 302 | */ |
||
| 303 | this.getTransition = function () { |
||
| 304 | return transition |
||
| 305 | } |
||
| 306 | |||
| 307 | /** |
||
| 308 | * @param {TStateId} id |
||
| 309 | * @param {THints} [hints] |
||
| 310 | * |
||
| 311 | * @return {Thenable} |
||
| 312 | */ |
||
| 313 | this.transitionTo = function (id, hints) { |
||
| 314 | if (stage.restricted) { |
||
| 315 | var message = 'State machine is in ' + stage.id + ' state ' + |
||
| 316 | 'and doesn\'t accept #transitionTo() calls' |
||
| 317 | throw new ScenarioError(message) |
||
| 318 | } |
||
| 319 | return transitionTo(requireState(id), hints) |
||
| 320 | } |
||
| 321 | |||
| 322 | /** |
||
| 323 | * Runs state machine |
||
| 324 | * |
||
| 325 | * @param {THints} [hints] |
||
| 326 | * @return {Thenable.<TStateMachineResult>} |
||
| 327 | */ |
||
| 328 | this.run = function (hints) { |
||
| 329 | transitionTo(entrypoint, hints) |
||
| 330 | return termination |
||
| 331 | } |
||
| 332 | |||
| 333 | /** |
||
| 334 | * @return {StateMachine.Stage} |
||
| 335 | */ |
||
| 336 | this.getStatus = function () { |
||
| 337 | return stage |
||
| 338 | } |
||
| 339 | |||
| 340 | /** |
||
| 341 | * Returns 100 last transition history events |
||
| 342 | * |
||
| 343 | * @return {TTransitionHistoryEntry[]} |
||
| 344 | */ |
||
| 345 | this.getHistory = function () { |
||
| 346 | return history |
||
| 347 | } |
||
| 348 | |||
| 349 | /** |
||
| 350 | * Returns termination handle |
||
| 351 | * |
||
| 352 | * @return {Thenable.<TStateMachineResult>} |
||
| 353 | */ |
||
| 354 | this.getTermination = function () { |
||
| 355 | return termination |
||
| 356 | } |
||
| 357 | } |
||
| 358 | |||
| 399 |