{"id":13562763,"url":"https://github.com/vstirbu/fsm-as-promised","last_synced_at":"2025-04-08T12:10:50.606Z","repository":{"id":17029492,"uuid":"19793606","full_name":"vstirbu/fsm-as-promised","owner":"vstirbu","description":"A finite state machine library using ES6 promises","archived":false,"fork":false,"pushed_at":"2021-11-24T05:54:18.000Z","size":250,"stargazers_count":460,"open_issues_count":7,"forks_count":18,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-04-01T11:04:11.644Z","etag":null,"topics":["es6-promise","finite-state-machine","fsm","hacktoberfest","javascript","promise","state-machine"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vstirbu.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-05-14T19:42:22.000Z","updated_at":"2025-03-09T21:31:04.000Z","dependencies_parsed_at":"2022-09-07T02:20:19.461Z","dependency_job_id":null,"html_url":"https://github.com/vstirbu/fsm-as-promised","commit_stats":null,"previous_names":[],"tags_count":39,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vstirbu%2Ffsm-as-promised","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vstirbu%2Ffsm-as-promised/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vstirbu%2Ffsm-as-promised/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vstirbu%2Ffsm-as-promised/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vstirbu","download_url":"https://codeload.github.com/vstirbu/fsm-as-promised/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247838445,"owners_count":21004580,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["es6-promise","finite-state-machine","fsm","hacktoberfest","javascript","promise","state-machine"],"created_at":"2024-08-01T13:01:12.044Z","updated_at":"2025-04-08T12:10:50.590Z","avatar_url":"https://github.com/vstirbu.png","language":"JavaScript","funding_links":[],"categories":["JavaScript","Libraries"],"sub_categories":["JavaScript"],"readme":"![Finite State Machine as Promised](https://raw.github.com/vstirbu/fsm-as-promised/master/fsm-as-promised.png)\n\nA minimalistic finite state machine library for browser and node implemented using [promises](http://promises-aplus.github.io/promises-spec/).\n\n[![NPM Version](https://img.shields.io/npm/v/fsm-as-promised.svg)](https://www.npmjs.com/package/fsm-as-promised)\n[![NPM License](https://img.shields.io/npm/l/fsm-as-promised.svg)](https://www.npmjs.com/package/fsm-as-promised)\n[![Build Status](https://travis-ci.org/vstirbu/fsm-as-promised.svg?branch=master)](https://travis-ci.org/vstirbu/fsm-as-promised)\n[![Coverage Status](https://coveralls.io/repos/vstirbu/fsm-as-promised/badge.svg?branch=master\u0026service=github)](https://coveralls.io/github/vstirbu/fsm-as-promised?branch=master)\n[![Code Climate](https://codeclimate.com/github/vstirbu/fsm-as-promised/badges/gpa.svg)](https://codeclimate.com/github/vstirbu/fsm-as-promised)\n[![Greenkeeper badge](https://badges.greenkeeper.io/vstirbu/fsm-as-promised.svg)](https://greenkeeper.io/)\n[![Known Vulnerabilities](https://snyk.io/test/github/vstirbu/fsm-as-promised/90f8f3e9021c20c4bcd4a6719385d2df687d2ac2/badge.svg)](https://snyk.io/test/github/vstirbu/fsm-as-promised/90f8f3e9021c20c4bcd4a6719385d2df687d2ac2)\n[![NPM Downloads](https://img.shields.io/npm/dm/fsm-as-promised.svg)](https://www.npmjs.com/package/fsm-as-promised)\n\n[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/vstirbu/fsm-as-promised?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge)\n\n:loudspeaker: For Visual Studio Code users: checkout the UML visualization [extension](https://marketplace.visualstudio.com/items?itemName=vstirbu.fsm-viewer).\n\n\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\n\u003c!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --\u003e\n\n\n- [How to use](#how-to-use)\n  - [Installation and Setup](#installation-and-setup)\n- [Configuring promise library](#configuring-promise-library)\n- [Create finite state machine](#create-finite-state-machine)\n  - [Define events](#define-events)\n  - [Define callbacks](#define-callbacks)\n  - [Initialisation options](#initialisation-options)\n    - [Initial state](#initial-state)\n    - [Final states](#final-states)\n    - [Target](#target)\n  - [Custom error handler](#custom-error-handler)\n- [Callbacks](#callbacks)\n  - [Arguments](#arguments)\n  - [Synchronous](#synchronous)\n  - [Asynchronous](#asynchronous)\n  - [Call order](#call-order)\n  - [Returned values](#returned-values)\n    - [Passing data between callbacks](#passing-data-between-callbacks)\n    - [Beyond the library boundary](#beyond-the-library-boundary)\n  - [Configuring callback prefix](#configuring-callback-prefix)\n- [Common Methods](#common-methods)\n- [Emitted Events](#emitted-events)\n- [Handling Errors](#handling-errors)\n  - [Graceful error recovery](#graceful-error-recovery)\n- [Recipes](#recipes)\n  - [Conditional transitions](#conditional-transitions)\n- [Tooling](#tooling)\n  - [Visual Studio Code extension](#visual-studio-code-extension)\n  - [UML visualization](#uml-visualization)\n- [Contributing](#contributing)\n- [License](#license)\n- [Credits](#credits)\n\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\n\n## How to use\n\n### Installation and Setup\n\nRun ```npm install fsm-as-promised``` to get up and running. Then:\n\n```javascript\n// ES5\nconst StateMachine = require('fsm-as-promised');\n\n// ES6\nimport StateMachine from 'fsm-as-promised';\n```\n\n## Configuring promise library\n\n```javascript\nStateMachine.Promise = YourChoiceForPromise\n```\n\nYou can choose from the following promise libraries:\n\n* [bluebird](https://github.com/petkaantonov/bluebird),\n* [lie](https://github.com/calvinmetcalf/lie),\n* [native-promise-only](https://github.com/getify/native-promise-only),\n* [pinkie](https://github.com/floatdrop/pinkie),\n* [promise](https://github.com/then/promise),\n* [RSVP](https://github.com/tildeio/rsvp.js),\n* [Q](https://github.com/kriskowal/q),\n* [when](https://github.com/cujojs/when).\n\nIf the environment does not provide ```Promise``` support, the default implementation is [es6-promise](https://github.com/jakearchibald/ES6-Promises).\n\nThe library works also with the promise implementation bundled with [es6-shim](https://github.com/paulmillr/es6-shim).\n\n## Create finite state machine\n\nA state machine object can be created by providing a configuration object:\n\n```javascript\nconst fsm = StateMachine({\n  events: [\n    { name: 'wait', from: 'here'},\n    { name: 'jump', from: 'here', to: 'there' },\n    { name: 'walk', from: ['there', 'somewhere'], to: 'here' }\n  ],\n  callbacks: {\n    onwait: function () {\n      // do something when executing the transition\n    },\n    onleavehere: function () {\n      // do something when leaving state here\n    },\n    onleave: function () {\n      // do something when leaving any state\n    },\n    onentersomewhere: function () {\n      // do something when entering state somewhere\n    },\n    onenter: function () {\n      // do something when entering any state\n    },\n    onenteredsomewhere: function () {\n      // do something after entering state somewhere\n      // transition is complete and events can be triggered safely\n    },\n    onentered: function () {\n      // do something after entering any state\n      // transition is complete and events can be triggered safely\n    }\n  }\n});\n```\n\n### Define events\n\nThe state machine configuration contains an array of event that convey information about what transitions are possible. Typically a transition is triggered by an event identified by _name_, and happens between _from_ and _to_ states.\n\n### Define callbacks\n\nThe state machine configuration can define callback functions that are invoked when leaving or entering a state, or during the transition between the respective states. The callbacks must return promises or be thenable.\n\n### Initialisation options\n\n#### Initial state\n\nYou can define the initial state by setting the initial property:\n\n```javascript\nconst fsm = StateMachine({\n  initial: 'here',\n  events: [\n    { name: 'jump', from: 'here', to: 'there' }\n  ]\n});\n\nconsole.log(fsm.current);\n// here\n```\n\notherwise the finite state machine's initial state is `none`.\n\n#### Final states\n\nYou can define the final state or states by setting the final property:\n\n```javascript\nconst fsm = StateMachine({\n  initial: 'here',\n  final: 'there', //can be a string or array\n  events: [\n    { name: 'jump', from: 'here', to: 'there' }\n  ]\n});\n```\n\n#### Target\n\nAn existing object can be augmented with a finite state machine:\n\n```javascript\nconst target = {\n      key: 'value'\n    };\n\nStateMachine({\n  events: [\n    { name: 'jump', from: 'here', to: 'there' }\n  ],\n  callbacks: {\n    onjump: function (options) {\n      // accessing target properties\n      console.log(target.key === this.key);\n    }\n  }\n}, target);\n\ntarget.jump();\n```\n\n### Custom error handler\n\nYou can override the default library error handler by setting the `error` property:\n\n```javascript\nconst fsm = StateMachine({\n  initial: 'red',\n  events: [\n    { name: 'red', from: 'green', to: 'red' }\n  ],\n  error: function customErrorHandler(msg, options) {\n    throw new Error('my error');\n  }\n});\n```\n\nThe value of the `error` property is a function that expects two arguments:\n\n* _msg_ a string containing the error reason\n* _options_ an object havin as properties the `name` of the transition and the `from` state when the error occurred.  \n\n## Callbacks\n\n### Arguments\n\nThe following arguments are passed to the callbacks:\n\n```javascript\nconst fsm = StateMachine({\n    events: [\n      { name: 'jump', from: 'here', to: 'there' }\n    ],\n    callbacks: {\n      onjump: function (options) {\n        // do something with jump arguments\n        console.log(options.args);\n\n        // do something with event name\n        console.log(options.name);\n\n        // do something with from state\n        console.log(options.from);\n\n        // do something with to state\n        console.log(options.to);\n\n        return options;\n      }\n    }\n  });\n\nfsm.jump('first', 'second');\n```\n\n### Synchronous\n\nYou can define synchronous callbacks as long as the callback returns the options object that is going to be passed to the next callback in the chain:\n\n```javascript\nconst fsm = StateMachine({\n    events: [\n      { name: 'jump', from: 'here', to: 'there' }\n    ],\n    callbacks: {\n      onjump: function (options) {\n        // do something\n\n        return options;\n      }\n    }\n  });\n\nfsm.jump();\n```\n\n### Asynchronous\n\nYou can define asynchronous callbacks as long as the callback returns a new promise that resolves with the options object when the asynchronous operation is completed. If the asynchronous operation is unsuccessful, you can throw an error that will be propagated throughout the chain.\n\n```javascript\nconst fsm = StateMachine({\n    events: [\n      { name: 'jump', from: 'here', to: 'there' }\n    ],\n    callbacks: {\n      onjump: function (options) {\n        return new Promise(function (resolve, reject) {\n          // do something\n          resolve(options);\n        });\n      }\n    }\n  });\n\nawait fsm.jump();\n```\n\n### Call order\n\nThe callbacks are called in the following order:\n\n| callback | state in which the callback executes | \n| --- | --- |\n| onleave{stateName} | from |\n| onleave | from |\n| on{eventName} | _from_ |\n| onenter{stateName} | _from_ |\n| onenter | _from_ |\n| onentered{stateName} | to |\n| onentered | to |\n\nA state is _locked_ if there is an ongoing transition between two different states. While the state is locked no other transitions are allowed.\n\nIf the transition is not successful (e.g. an error is thrown from any callback), the state machine returns to the state in which it is executed.\n\n### Returned values\n\nBy default, each callback in the promise chain is called with the `options` object.\n \n#### Passing data between callbacks\n\nCallbacks can pass values that can be used by subsequent callbacks in the promise chain.\n\n```javascript\nconst fsm = StateMachine({\n  initial: 'one',\n  events: [\n    { name: 'start', from: 'one', to: 'another' }\n  ],\n  callbacks: {\n    onleave: function (options) {\n      options.foo = 2;\n    },\n    onstart: function (options) {\n      // can use options.foo value here\n      if (options.foo === 2) {\n        options.foo++;\n      }\n    },\n    onenter: function (options) {\n      // options.foo === 3\n    }\n  }\n});\n```\n\nThis also includes callbacks added to the chain by the user.\n\n```javascript\nfsm.start().then(function (options) {\n  // options.foo === 3\n});\n```\n\n#### Beyond the library boundary\n\nThe `options` object can be hidden from the promises added by the end user by setting the __options.res__ property. This way the subsequent promises that are not part of the state machine do not receive the `options` object.\n\n```javascript\nconst fsm = StateMachine({\n  initial: 'one',\n  events: [\n    { name: 'start', from: 'one', to: 'another' }\n  ],\n  callbacks: {\n    onstart: function (options) {\n      options.res = {\n        val: 'result of running start'\n      };\n    }\n  }\n});\n\nconst result = await fsm.start();\n\nconsole.log(result);\n```\n\n### Configuring callback prefix\n\nBy default, the callback names start with `on`. You can omit the prefix by setting it to empty string or assign any other prefix:\n\n```javascript\nStateMachine.callbackPrefix = 'customPrefix';\n```\n\n## Common Methods\n\nThe library adds the following utilities to the finite state machine object:\n\n* `can(event)` checks if the _event_ can be triggered in the current state,\n* `cannot(event)` checks if the _event_ cannot be triggered in the current state,\n* `is(state)` checks if the _state_ is the current state,\n* `isFinal(state)` checks if the _state_ is final state. If no state is provided the current state is checked. \n* `hasState(state)` checks if the finite state machine has the _state_.\n* `instanceId` returns the uuid of the instance\n\n## Emitted Events\n\nThe finite state machine object is an `EventEmitter`. By default, the library emits `state` event whenever the state machine enters a new state.\n\nYou can define and emit new events.\n\n## Handling Errors\n\nErrors thrown by any of the callbacks called during a transition are propagated through the promise chain and can be handled like this:\n\n```javascript\nfsm.jump().catch(function (err) {\n  // do something with err...\n  // err.trigger - the event that triggered the error\n  // err.current - the current state of the state machine\n  // err.message - described bellow...\n  // err.pending - an object containing the description of intra-state pending transitions\n});\n```\n\nThe library throws errors with the following messages:\n\n| message | explanation | note |\n| --- | --- | --- |\n| Ambigous transition | The state machine has one transition that starts from one state and ends in multiple | must be fixed during design time |\n| Previous transition pending | The previous intra-state transition(s) is in progress preventing new ones until it has completed | - |\n| Previous inter-state transition started | Inter-state transition started | - |\n| Invalid event in current state | The state machine is in a state that does not allow the requested transition | - |\n\n:warning: Unhandled errors may lead to inconsistent state machine. If you reserved resources as part of a transition, you have to release them if an error occured.\n\n### Graceful error recovery\n\nIt is not advisable to let the errors that can be handled gracefully at callback level to propagate to the end of the promise chain.\n\nThe following is an example where the error is handled inside a synchronous callback:\n\n```javascript\nconst fsm = StateMachine({\n  initial: 'green',\n  events: [\n    { name: 'warn',  from: 'green',  to: 'yellow' }\n  ],\n  callbacks: {\n    onwarn: function (options) {\n      try {\n        throw new Error('TestError');\n      } catch (err) {\n        // handle error\n        return options;\n      }\n    }\n  }\n});\n\nawait fsm.warn()\n\nfsm.current === 'yellow';\n// true\n```\n\nThe same inside an asynchronous callback:\n\n```javascript\nconst fsm = StateMachine({\n  initial: 'green',\n  events: [\n    { name: 'warn',  from: 'green',  to: 'yellow' }\n  ],\n  callbacks: {\n    onwarn: function (options) {\n      return new StateMachine.Promise(function (resolve, reject) {\n        reject(new Error('TestError'));\n      }).catch(function (err) {\n        // handle error\n        return options;\n      });\n    }\n  }\n});\n  \nawait fsm.warn()\n\nfsm.current === 'yellow';\n// true\n```\n\n## Recipes\n\n### Conditional transitions\n\nThe library provides a way to define conditional transitions:\n\n```javascript\nStateMachine({\n  events: [\n    { name: 'conditional',\n      from: 'init',\n      to: ['one', 'two'],\n      condition: function (options) {\n        return 0; // transition to state 'one'\n      }\n    }\n  ]\n});\n```\n\nThe above is equivalent to:\n\n```javascript\nStateMachine({\n  events: [\n    { name: 'conditional',\n      from: 'init',\n      to: ['one', 'two'],\n      condition: function (options) {\n        return 'one'; // transition to state 'one'\n      }\n    }\n  ]\n});\n```\n\nThe condition callback must return the `to` Array's index of the selected state, the name of the selected state, or a promise which resolves to either.  The condition callback is executed after `on{eventName}` callback. \n\nIf the above is not suitable, complex conditional transitions can be achieved through transitioning explicitly to a pseudo state where the condition is checked, then the appropriate event is triggered:\n\n```javascript\nStateMachine({\n  events: [\n    { name: 'trigger', from: 'existing', to: 'pseudo' },\n    { name: 'triggerOptionA', from: 'pseudo', to: 'option-a' },\n    { name: 'triggerOptionB', from: 'pseudo', to: 'option-b' }\n  ],\n  callbacks: {\n    onenteredpseudo: function () {\n      if (condition) {\n        this.triggerOptionA();\n      } else {\n        this.triggerOptionB();\n      }\n    }\n  }\n});\n```\n\nIf your pseudo state's callback returns a Promise, you must return the call to the event function; e.g. `return this.triggerOptionA()`.\n\n## Tooling\n\n### Visual Studio Code extension\n\nYou can visualize the state machine as a UML diagram in vscode using the [Finite state machine viewer](https://marketplace.visualstudio.com/items?itemName=vstirbu.fsm-viewer) extension.\n\n\n### UML visualization\n\nThe state machine definitions can be visualized as UML diagrams using [fsm2dot](https://github.com/vstirbu/fsm2dot).\n\nInstall fsm2dot and [graphviz](http://www.graphviz.org/), then:\n\n```bash\nfsm2dot -f fsm.js -o fsm.dot\ndot -Tpdf fsm.dot -o fsm.pdf\n```\n\n## Contributing\n\nInstall the library and run tests:\n\n```\nnpm install\nnpm test\n```\n\n## License\n\nThe library is available under the MIT license.\n\n## Credits\n\nThe framework is heavily influenced by Jake Gordon's [javascript-state-machine](https://github.com/jakesgordon/javascript-state-machine).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvstirbu%2Ffsm-as-promised","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvstirbu%2Ffsm-as-promised","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvstirbu%2Ffsm-as-promised/lists"}