npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

fsm-iterator

v1.1.0

Published

A finite state machine iterator for JavaScript

Downloads

659,914

Readme

fsm-iterator

Travis branch npm

A finite state machine iterator for JavaScript.

Use fsm-iterator to implement a finite state machine generator function without the need for generator function* syntax. This is a perfect for library authors that need to use generators but don't want to burden their ES5 users with a transpiled generator runtime.

Install

npm install --save fsm-iterator

Usage

The default export is the fsmIterator function. It takes an initial state as the first argument and the state machine as an object literal as the second argument. Each key-value pair of your definition is a state and a function to handle that state. States can be strings or symbols.

The state function takes any value that was passed into the next method of the iterator as the first argument and the finite state machine definition itself as the second argument. This allows you to act on values passed back into the "generator" and delegate to other states. The finite state machine argument also includes a previousState property if you need to use it.

To yield a value from your state function return an object literal with the value property set to the yielded value. To specify the next state to transition to, set the next property to the next state. You can end the iterator by including the done property set to true. If you don't supply the next property, then the iterator will stay in the same state. This is fine if you want to loop on one thing, but if you have multiple states, then remember to use the next property.

You may include a throw function to handle the throw method of the iterator. It takes the thrown error as the first argument and the finite state machine definition as the second argument. If you don't supply a throw function, then your iterator will stop, rethrowing the error.

You may intercept the iterator's return method by supplying a return function that receives the passed-in return value as the first argument and the finite state machine definition as the second argument. You can return your own return value or keep your iterator returning. This is similar to handling the finally block in a try..finally statement in a ES2015 generator function.

// ES2015 modules
import fsmIterator from 'fsm-iterator';

// ES5 and CJS
var fsmIterator = require('fsm-iterator').default;

const FOO = 'FOO';
const BAR = 'BAR';
const BAZ = 'BAZ';

const definition = {
  [FOO]: () => ({
    value: 'foo',
    next: BAR,
  }),

  [BAR](x) {
    if (x < 0) {
      return {
        value: x / 2,
        done: true,
      };
    }

    return {
      value: x * 2,
      next: BAZ,
    };
  },

  [BAZ]: (_, fsm) => ({
    value: `baz : ${fsm.previousState}`,
    next: FOO,
  }),

  return(value, fsm) {
    return {
      value: 'my own return',
      done: true,
    }
  },

  throw: (e, fsm) => ({
    value: `${e.message} : ${fsm.previousState}`,
    next: FOO,
  }),
};

// Normal path
let iterator = fsmIterator(FOO, definition);

iterator.next();    // { value: 'foo', done: false }
iterator.next(21);  // { value: 42, done: false }
iterator.next();    // { value: 'baz : BAR', done: false }
iterator.next();    // { value: 'foo', done: false }
iterator.next(-42); // { value: -21, done: true }

// Throwing
const error = new Error('error');
iterator = fsmIterator(FOO, definition);

iterator.next();       // { value: 'foo', done: false }
iterator.next(21);     // { value: 42, done: false }
iterator.throw(error); // { value: 'error : BAR', done: false }
iterator.next();       // { value: 'foo', done: false }

// Returning
iterator = fsmIterator(FOO, definition);

iterator.next();            // { value: 'foo', done: false }
iterator.next(21);          // { value: 42, done: false }
iterator.return('the end'); // { value: 'my own return', done: true }

Equivalent ES2015 Generator

Here is the comparable ES2015 generator for the previous example.

const FOO = 'FOO';
const BAR = 'BAR';
const BAZ = 'BAZ';

function* myGenerator() {
  let currentState = FOO;
  let previousState = null;

  function setState(newState) {
    previousState = currentState;
    currentState = newState;
  }

  while (true) {
    try {
      const x = yield 'foo';

      setState(BAR);

      if (x < 0) {
        return x / 2;
      }

      yield x * 2;

      setState(BAZ);

      yield `baz : ${previousState}`;

      setState(FOO);
    } catch (e) {
      setState(FOO);

      yield `${e.message} : ${previousState}`;
    } finally {
      return 'my own return';
    }
  }
}

Another Example

Here is the implementation of the router saga from redux-saga-router using fsmIterator.

import { call, take } from 'redux-saga/effects';
import fsmIterator from 'fsm-iterator';
import buildRouteMatcher from './buildRouteMatcher';
import createHistoryChannel from './createHistoryChannel';

const INIT = 'INIT';
const LISTEN = 'LISTEN';
const HANDLE_LOCATION = 'HANDLE_LOCATION';

export default function router(history, routes) {
  const routeMatcher = buildRouteMatcher(routes);
  let historyChannel = null;
  let lastMatch = null;

  function errorMessageValue(error, message) {
    let finalMessage = `Redux Saga Router: ${message}:\n${error.message}`;

    if ('stack' in error) {
      finalMessage += `\n${error.stack}`;
    }

    return {
      value: call([console, console.error], finalMessage),
      next: LISTEN,
    };
  }

  return fsmIterator(INIT, {
    [INIT]: () => ({
      value: call(createHistoryChannel, history),
      next: LISTEN,
    }),

    [LISTEN](channel) {
      if (channel) {
        historyChannel = channel;
      }

      return {
        value: take(historyChannel),
        next: HANDLE_LOCATION,
      };
    },

    [HANDLE_LOCATION](location, fsm) {
      const path = location.pathname;
      const match = routeMatcher.match(path);

      if (match) {
        lastMatch = match;

        return {
          value: call(match.action, match.params),
          next: LISTEN,
        };
      }

      return fsm[LISTEN]();
    },

    throw(e, fsm) {
      switch (fsm.previousState) {
        case HANDLE_LOCATION:
          return errorMessageValue(e, `Unhandled ${e.name} in route "${lastMatch.route}"`);

        case LISTEN:
          return errorMessageValue(e, `Unexpected ${e.name} while listening for route`);

        default:
          return { done: true };
      }
    },
  });
}