quantum-flux

A non-blocking Flux/React dispatcher and stores.

Usage no npm install needed!

<script type="module">
  import quantumFlux from 'https://cdn.skypack.dev/quantum-flux';
</script>

README

quantum-flux

====

Build Status

Installation

  npm install quantum-flux

Table of Contents

Introduction

Flux is the data-flow architecture associated with the React UI Framework. In brief it proposes an application composed of three major parts: the dispatcher, it's stores, and their views (React Components).

Data flows through such an application in a single, cyclic, direction:

Views ---> (actions) ---> Dispatcher ----> (callback) ----> Stores ----+
^                                                                      |
|                                                                      V
+--------( "change" event handlers) <------ ("change" events) ---------+

Views generate actions, propogated to a dispatcher, the dispatcher informs data stores about the event and they update themselves appropriately, stores fire "change" events which views listen to vis-a-vis event handlers and so on...a single glorious cycle.

A full introduction to Flux Architecture can be found @ http://facebook.github.io/react/docs/flux-overview.html

Why quantum-flux?

Because all actions in an application propogate to the application dispatcher, which in turn funnels them universally to all application stores, there is a natural single-thread processing bottleneck that will occur as an application grows.

In a hypothetically large application with many stores and many more actions, it begins to make sense to think about the idea of non-blocking dispatch resolution, enabling Javascript based-timers, CSS animations, and UI interactions to continue while the dispatch phase of a flux-cycle complete.

An asynchronous dispatcher would allow the application to queue up additional call to the dispatcher (even recursive calls) processing them asynchronously, while ensuring that stores own change event handlers were themselves called synchronously and in the correct total ordering of events.

In effect we enforce a total state transition between two phases: a dispatch or "action" phase and the render or "react" phase. It looks something like this:

                                                        
           An action occurs,                                        
            moving application into a still                 Application state (all stores)
            resolving state that is neither               is now fully transitioned from 
              fully A nor B                                     state A to B.
              |                   --->                               |  
              |       -->> Asynchronous Resolution Phase -->         |
   STATE  A   |                           -->                        |     STATE B
              V          (ACTION/FLUX  PHASE)                        V  (REACT PHASE) 
Views ---> (actions) ---> Dispatcher ----> (callback) ----> Stores ----+
^                                                                      |   
|                                                                      V
+--------( "change" event handlers) <------ ("change" events) ---------+
   ^
   |                <-- Synchronous Resolution Phase <--
   |
  (END REACT PHASE)
 1x BROWSER REPAINT, STATE B

This is quantum-flux.

The synchronous resolution of the "react phase" ensures that responsive UI repaints remain atomic and correctly ordered, just as they would be in a pure synchronous dispatcher implementation, while supporting a non-blocking "action/flux phase."

Basic Usage

  //Create a flux dispatcher. ********************************************
  
  var Quantum = require('quantum-flux');
  
  var flux = new Quantum();

  //Register a store. ****************************************************

  var store = {
    data: null
  };
  
  flux.register(store, function (payload) {
    //Do something when there is an appropriate payload
    switch (payload.isRelevant) {

    case "yes": 
      //update the store's data.
      this.data = payload.data;
      break;

    default:
      return;
    }
    //notify any listeners that there was a change.
    this.changed();
  });

  //Add a listener to this store. ****************************************

  store.onChange(function () {
    //do something with the store data when it's updated.
    console.log(store.data);
  });

  //Dispatch an action! **************************************************

  flux.dispatch({
    isRelevant: "yes",
    data: 42
  });

API Documentation

--

the dispatcher

Quantum

Quantum()

--

Constructor

Parameters
none
Example
  var Quantum = require('quantum-flux');

  var flux = new Quantum();

--

Quantum.dispatch

Quantum.dispatch(payload)

--

Description:

Dispatch the argument payload to all registered stores.

Parameters
Name Type Description
payload any The thing to be dispatched, can be falsey.
Returns

undefined

Example
  var flux = new Quantum(),

  flux.dispatch({
    actionType: 'first quantum dispatch!',
    value: 'hello, quantum-flux!'
  });

--

Quantum.register

Quantum.register(store, listener)

--

Description:

Register a new store and it's listener with the dispatcher. If store is already registered with the dispatcher, changes stores current listening function to listener.

Parameters
Name Type Description
store {object} The dispatcher will automatically extend "plain" Objects with Quantum.Store.prototype.
listener {function} The store's callback for dispatches; will be called as: listener.apply(store, [args]);
Returns

store

Example
  var flux = new Quantum();

  var store = {
    foo: "bar",
    fn: function (payload) {
      if (payload.actionType === "FOO_UPDATE") {
        this.foo = payload.value;
        this.changed();
      }
    }
  };
  
  flux.register(store, store.fn);

--

Quantum.unregister

Quantum.unregister(store)

--

Description

Unregister store with the dispatcher. The listening function associated with store will no longer be informed about dispatch events.

Parameters
Name Type Description
store {object} The store to be unregistered with the flux dispatcher.
#####Returns
store
Example
  //Assume `store` is a previously registered store.
  
  flux.unregister(store);

--

the stores

About Stores

Stores implement an EventEmitter prototype very similiar to the Node.js EventEmitter prototype with one exception: the behavior of removeListener is consistent with the browser behavior of removeEventListener not Node's removeListener behavior.

Please consult the Node.js documentation for complete API documentation for the EventEmitter prototype:

http://nodejs.org/api/events.html

Example

var flux = new Quantum;

var store = {};

flux.register(store, function () {});

var f1 = function () {
  store.removeListener('change', f2); // or aliases - #removeEventListener() or #off()
};

var f2 = function () {
};

store.addListener('change', f1); //or aliases - #addEventListener() or #on()
store.addListener('change', f2);

store.emit('change');

//f2 was not called, as f1 removed it.  This is consistent with the
//in browser behavior of `removeEventListener`.
 
//In node.js f2 would have been called, but would not be called the
//next time the `change` event was fired.

Store.waitFor

Store.waitFor([stores], onFulfilled, onRejected)

--

Description

When called inside a stores registered dispatch handler, instructs the dispatcher to defer execution of the onFulfilled or onRejected callbacks until the store or array of stores specified by [stores] have first completedthe current dispatch cycle.

--

Store.addChangeListener

Store.addChangeListener(func)

--

Description

A convenience method for:

Store.on('change', func);
Parameters
Name Type Description
func {function} The function handler for store change events.
#####returns
undefined
#####Aliases
######onChange()
#####Example
var flux = new Quantum();

var store = {};

flux.register(store, function () {});

var f = function () { /*...*/ };

store.addChangeListener(f);

--

Store.removeChangeListener

Store.removeChangeListener(func)

--

Description

A convenience method for:

Store.off('change', func);
Parameters
Name Type Description
func {function} The handler to desubscribe from change events.
#####returns
undefined
#####Aliases
######offChange()
#####Example
var flux = new Quantum();

var store = {};

flux.register(store, function () {});

var f = function () { /*...*/ };

store.addChangeListener(f);

//stop listening to this store's change event with `f`
store.removeChangeListener(f);

--

Store.changed

Store.changed([args])

--

Description

A convenience method for:

Store.emit('change', /* [args] */);
Parameters
Name Type Description
args... any Optional arguments to pass to event listeners for the change event.
#####Returns
undefined
Example
var flux = new Quantum();
var store = {};

flux.register(store, function (payload) {
  if (payload.isRelevantToThisStore) {

    //it updates the store appropriately.

    this.changed();
  }
})

store.onChange(/*...etc...*/);

Advanced APIs

Quantum.setImmediate

Quantum.setImmediate(func)

--

Description

Preempt the next dispatch in the dispatch queue with the async evaluation of func. A browser repaint will be allowed both before and after func is evaluated, then dispatching will continue in it's "natural"* order. If func dispatches an action with Quantum.dispatch(), that dispatch will also precede the natural queue order...and so on.

It can be hard to notice the effects of setImmediate unless func issues a call to Quantum#dispatch() in all cases, however, setImmediate has the effect of "padding" the execution of func with browser repaints, i.e., it is asynchronous both before and after.

Parameters
Name Type Description
func {function} The function to execute prior to the next dispatch in the "natural"* order.

* We can define "natural" order as the order of dispatch events queued such that registered stores fire their own changed listeners and other events in attached order and stores do this in in the order of dispatch resolution...i.e., if storeA waitedFor storeB, storeB fires it's own changes prior to storeA.

It's important to remember that the quantum-flux dispatcher supports recursive calls to dispatch as part of the natural order.

Example
  var flux = new Quantum();

  var store1 = flux.register({}, function (p) {
    this.changed();
  });

  store1.addChangeListener(function () {
    console.log('logged synchronously');
  });

  var store2 = flux.register({}, function (p) {
    this.changed();
  });

  store2.addChangeListener(function (p) {
    flux.setImmediate(function () {
      //The thread 'sleeps' directly before this...browser can repaint.
      
      console.log('executed asynchronously, but still in order');
      
      //And 'sleeps' directly after this again...browser can repaint.
    });
  });

  flux.dispatch();

  //logged synchronously
  //executed asynchronously, but still in order

Quantum.interlace

Quantum.interlace()

--

Description

Use maximum thread-sharing when processing flux-store dispatch digestion. This will increase the number of browser repaints available and/or allow other javascript processes (such as timers) to run while stores digest the current dispatch.

If interlacing is already enabled, the call has no effect.

By default, the quantum-flux dispatcher only thread-shares when processing a waitFor() or setImmediate() request.

Default
thead-interlacing is disabled.
Parameters
none
Returns

undefined

Example
  var flux = new Quantum();

  flux.interlace();
  

Quantum.deInterlace

Quantum.deInterlace()

--

Description

Turn off thread-interlacing if it has been enabled, otherwise has no effect. Interlacing is disabled by default.

Default
thread-interlacing is disabled.
Parameters
none
Returns

undefined

Example
  var flux = new Quantum();

  flux.interlace();
  flux.deInterlace();
  

Contributing to Quantum Flux

  • 2 space indentation
  • 80 columns
  • Include unit tests in your pull requests