Guest User

TypeScript Flux Dispatcher

a guest
Jan 7th, 2015
563
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