daily pastebin goal
57%
SHARE
TWEET

TypeScript Flux Dispatcher

a guest Jan 7th, 2015 382 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*
  2. * Copyright (c) 2014, Facebook, Inc.
  3. * All rights reserved.
  4. *
  5. * This source code is licensed under the BSD-style license found in the
  6. * LICENSE file in the root directory of this source tree. An additional grant
  7. * of patent rights can be found in the PATENTS file in the same directory.
  8. *
  9. * @providesModule Dispatcher
  10. * @typechecks
  11. */
  12.  
  13. // 2014-10-16 Dan Roberts: Minor adjustments for use as TypeScript with support for the option "Allow implicit 'any' types" to be disabled. The copyright message is maintained from the original file at https://github.com/facebook/flux/blob/master/src/Dispatcher.js
  14.  
  15. import invariant = require('./invariant');
  16.  
  17. var _lastID = 1;
  18. var _prefix = 'ID_';
  19.  
  20. /**
  21.  * Dispatcher is used to broadcast payloads to registered callbacks. This is
  22.  * different from generic pub-sub systems in two ways:
  23.  *
  24.  *   1) Callbacks are not subscribed to particular events. Every payload is
  25.  *      dispatched to every registered callback.
  26.  *   2) Callbacks can be deferred in whole or part until other callbacks have
  27.  *      been executed.
  28.  *
  29.  * For example, consider this hypothetical flight destination form, which
  30.  * selects a default city when a country is selected:
  31.  *
  32.  *   var flightDispatcher = new Dispatcher();
  33.  *
  34.  *   // Keeps track of which country is selected
  35.  *   var CountryStore = {country: null};
  36.  *
  37.  *   // Keeps track of which city is selected
  38.  *   var CityStore = {city: null};
  39.  *
  40.  *   // Keeps track of the base flight price of the selected city
  41.  *   var FlightPriceStore = {price: null}
  42.  *
  43.  * When a user changes the selected city, we dispatch the payload:
  44.  *
  45.  *   flightDispatcher.dispatch({
  46.  *     actionType: 'city-update',
  47.  *     selectedCity: 'paris'
  48.  *   });
  49.  *
  50.  * This payload is digested by `CityStore`:
  51.  *
  52.  *   flightDispatcher.register(function(payload) {
  53.  *     if (payload.actionType === 'city-update') {
  54.  *       CityStore.city = payload.selectedCity;
  55.  *     }
  56.  *   });
  57.  *
  58.  * When the user selects a country, we dispatch the payload:
  59.  *
  60.  *   flightDispatcher.dispatch({
  61.  *     actionType: 'country-update',
  62.  *     selectedCountry: 'australia'
  63.  *   });
  64.  *
  65.  * This payload is digested by both stores:
  66.  *
  67.  *    CountryStore.dispatchToken = flightDispatcher.register(function(payload) {
  68.  *     if (payload.actionType === 'country-update') {
  69.  *       CountryStore.country = payload.selectedCountry;
  70.  *     }
  71.  *   });
  72.  *
  73.  * When the callback to update `CountryStore` is registered, we save a reference
  74.  * to the returned token. Using this token with `waitFor()`, we can guarantee
  75.  * that `CountryStore` is updated before the callback that updates `CityStore`
  76.  * needs to query its data.
  77.  *
  78.  *   CityStore.dispatchToken = flightDispatcher.register(function(payload) {
  79.  *     if (payload.actionType === 'country-update') {
  80.  *       // `CountryStore.country` may not be updated.
  81.  *       flightDispatcher.waitFor([CountryStore.dispatchToken]);
  82.  *       // `CountryStore.country` is now guaranteed to be updated.
  83.  *
  84.  *       // Select the default city for the new country
  85.  *       CityStore.city = getDefaultCityForCountry(CountryStore.country);
  86.  *     }
  87.  *   });
  88.  *
  89.  * The usage of `waitFor()` can be chained, for example:
  90.  *
  91.  *   FlightPriceStore.dispatchToken =
  92.  *     flightDispatcher.register(function(payload) {
  93.  *       switch (payload.actionType) {
  94.  *         case 'country-update':
  95.  *           flightDispatcher.waitFor([CityStore.dispatchToken]);
  96.  *           FlightPriceStore.price =
  97.  *             getFlightPriceStore(CountryStore.country, CityStore.city);
  98.  *           break;
  99.  *
  100.  *         case 'city-update':
  101.  *           FlightPriceStore.price =
  102.  *             FlightPriceStore(CountryStore.country, CityStore.city);
  103.  *           break;
  104.  *     }
  105.  *   });
  106.  *
  107.  * The `country-update` payload will be guaranteed to invoke the stores'
  108.  * registered callbacks in order: `CountryStore`, `CityStore`, then
  109.  * `FlightPriceStore`.
  110.  */
  111. class Dispatcher {
  112.     private _callbacks: any;
  113.     private _isPending: any;
  114.     private _isHandled: any;
  115.     private _isDispatching: boolean;
  116.     private _pendingPayload: any;
  117.     constructor() {
  118.         this._callbacks = {};
  119.         this._isPending = {};
  120.         this._isHandled = {};
  121.         this._isDispatching = false;
  122.         this._pendingPayload = null;
  123.     }
  124.  
  125.     /**
  126.      * Registers a callback to be invoked with every dispatched payload. Returns
  127.      * a token that can be used with `waitFor()`.
  128.      *
  129.      * @param {function} callback
  130.      * @return {string}
  131.      */
  132.     public register(callback: any) {
  133.         var id = _prefix + _lastID++;
  134.         this._callbacks[id] = callback;
  135.         return id;
  136.     }
  137.  
  138.     /**
  139.      * Removes a callback based on its token.
  140.      *
  141.      * @param {string} id
  142.      */
  143.     public unregister(id: string) {
  144.         invariant(
  145.             this._callbacks[id],
  146.             'Dispatcher.unregister(...): `%s` does not map to a registered callback.',
  147.             id
  148.             );
  149.         delete this._callbacks[id];
  150.     }
  151.  
  152.     /**
  153.      * Waits for the callbacks specified to be invoked before continuing execution
  154.      * of the current callback. This method should only be used by a callback in
  155.      * response to a dispatched payload.
  156.      *
  157.      * @param {array<string>} ids
  158.      */
  159.     public waitFor(ids: string[]) {
  160.         invariant(
  161.             this._isDispatching,
  162.             'Dispatcher.waitFor(...): Must be invoked while dispatching.'
  163.             );
  164.         for (var ii = 0; ii < ids.length; ii++) {
  165.             var id = ids[ii];
  166.             if (this._isPending[id]) {
  167.                 invariant(
  168.                     this._isHandled[id],
  169.                     'Dispatcher.waitFor(...): Circular dependency detected while ' +
  170.                     'waiting for `%s`.',
  171.                     id
  172.                     );
  173.                 continue;
  174.             }
  175.             invariant(
  176.                 this._callbacks[id],
  177.                 'Dispatcher.waitFor(...): `%s` does not map to a registered callback.',
  178.                 id
  179.                 );
  180.             this._invokeCallback(id);
  181.         }
  182.     }
  183.  
  184.     /**
  185.      * Dispatches a payload to all registered callbacks.
  186.      *
  187.      * @param {object} payload
  188.      */
  189.     public dispatch(payload: {}) {
  190.         invariant(
  191.             !this._isDispatching,
  192.             'Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch.'
  193.             );
  194.         this._startDispatching(payload);
  195.         try {
  196.             var id: string;
  197.             for (id in this._callbacks) {
  198.                 if (this._isPending[id]) {
  199.                     continue;
  200.                 }
  201.                 this._invokeCallback(id);
  202.             }
  203.         } finally {
  204.             this._stopDispatching();
  205.         }
  206.     }
  207.  
  208.     /**
  209.      * Is this Dispatcher currently dispatching.
  210.      *
  211.      * @return {boolean}
  212.      */
  213.     public isDispatching() {
  214.         return this._isDispatching;
  215.     }
  216.  
  217.     /**
  218.      * Call the callback stored with the given id. Also do some internal
  219.      * bookkeeping.
  220.      *
  221.      * @param {string} id
  222.      * @internal
  223.      */
  224.     private _invokeCallback(id: string) {
  225.         this._isPending[id] = true;
  226.         this._callbacks[id](this._pendingPayload);
  227.         this._isHandled[id] = true;
  228.     }
  229.  
  230.     /**
  231.      * Set up bookkeeping needed when dispatching.
  232.      *
  233.      * @param {object} payload
  234.      * @internal
  235.      */
  236.     private _startDispatching(payload: {}) {
  237.         for (var id in this._callbacks) {
  238.             this._isPending[id] = false;
  239.             this._isHandled[id] = false;
  240.         }
  241.         this._pendingPayload = payload;
  242.         this._isDispatching = true;
  243.     }
  244.  
  245.     /**
  246.      * Clear bookkeeping used for dispatching.
  247.      *
  248.      * @internal
  249.      */
  250.     private _stopDispatching() {
  251.         this._pendingPayload = null;
  252.         this._isDispatching = false;
  253.     }
  254. }
  255.  
  256. export = Dispatcher;
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top