223 lines
7.8 KiB
JavaScript
223 lines
7.8 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*
|
|
* @providesModule Dispatcher
|
|
*
|
|
* @preventMunge
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
|
|
function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
|
|
var invariant = require("fbjs/lib/invariant");
|
|
var _prefix = 'ID_';
|
|
|
|
/**
|
|
* Dispatcher is used to broadcast payloads to registered callbacks. This is
|
|
* different from generic pub-sub systems in two ways:
|
|
*
|
|
* 1) Callbacks are not subscribed to particular events. Every payload is
|
|
* dispatched to every registered callback.
|
|
* 2) Callbacks can be deferred in whole or part until other callbacks have
|
|
* been executed.
|
|
*
|
|
* For example, consider this hypothetical flight destination form, which
|
|
* selects a default city when a country is selected:
|
|
*
|
|
* var flightDispatcher = new Dispatcher();
|
|
*
|
|
* // Keeps track of which country is selected
|
|
* var CountryStore = {country: null};
|
|
*
|
|
* // Keeps track of which city is selected
|
|
* var CityStore = {city: null};
|
|
*
|
|
* // Keeps track of the base flight price of the selected city
|
|
* var FlightPriceStore = {price: null}
|
|
*
|
|
* When a user changes the selected city, we dispatch the payload:
|
|
*
|
|
* flightDispatcher.dispatch({
|
|
* actionType: 'city-update',
|
|
* selectedCity: 'paris'
|
|
* });
|
|
*
|
|
* This payload is digested by `CityStore`:
|
|
*
|
|
* flightDispatcher.register(function(payload) {
|
|
* if (payload.actionType === 'city-update') {
|
|
* CityStore.city = payload.selectedCity;
|
|
* }
|
|
* });
|
|
*
|
|
* When the user selects a country, we dispatch the payload:
|
|
*
|
|
* flightDispatcher.dispatch({
|
|
* actionType: 'country-update',
|
|
* selectedCountry: 'australia'
|
|
* });
|
|
*
|
|
* This payload is digested by both stores:
|
|
*
|
|
* CountryStore.dispatchToken = flightDispatcher.register(function(payload) {
|
|
* if (payload.actionType === 'country-update') {
|
|
* CountryStore.country = payload.selectedCountry;
|
|
* }
|
|
* });
|
|
*
|
|
* When the callback to update `CountryStore` is registered, we save a reference
|
|
* to the returned token. Using this token with `waitFor()`, we can guarantee
|
|
* that `CountryStore` is updated before the callback that updates `CityStore`
|
|
* needs to query its data.
|
|
*
|
|
* CityStore.dispatchToken = flightDispatcher.register(function(payload) {
|
|
* if (payload.actionType === 'country-update') {
|
|
* // `CountryStore.country` may not be updated.
|
|
* flightDispatcher.waitFor([CountryStore.dispatchToken]);
|
|
* // `CountryStore.country` is now guaranteed to be updated.
|
|
*
|
|
* // Select the default city for the new country
|
|
* CityStore.city = getDefaultCityForCountry(CountryStore.country);
|
|
* }
|
|
* });
|
|
*
|
|
* The usage of `waitFor()` can be chained, for example:
|
|
*
|
|
* FlightPriceStore.dispatchToken =
|
|
* flightDispatcher.register(function(payload) {
|
|
* switch (payload.actionType) {
|
|
* case 'country-update':
|
|
* case 'city-update':
|
|
* flightDispatcher.waitFor([CityStore.dispatchToken]);
|
|
* FlightPriceStore.price =
|
|
* getFlightPriceStore(CountryStore.country, CityStore.city);
|
|
* break;
|
|
* }
|
|
* });
|
|
*
|
|
* The `country-update` payload will be guaranteed to invoke the stores'
|
|
* registered callbacks in order: `CountryStore`, `CityStore`, then
|
|
* `FlightPriceStore`.
|
|
*/
|
|
var Dispatcher = /*#__PURE__*/function () {
|
|
function Dispatcher() {
|
|
_defineProperty(this, "_callbacks", void 0);
|
|
_defineProperty(this, "_isDispatching", void 0);
|
|
_defineProperty(this, "_isHandled", void 0);
|
|
_defineProperty(this, "_isPending", void 0);
|
|
_defineProperty(this, "_lastID", void 0);
|
|
_defineProperty(this, "_pendingPayload", void 0);
|
|
this._callbacks = {};
|
|
this._isDispatching = false;
|
|
this._isHandled = {};
|
|
this._isPending = {};
|
|
this._lastID = 1;
|
|
}
|
|
|
|
/**
|
|
* Registers a callback to be invoked with every dispatched payload. Returns
|
|
* a token that can be used with `waitFor()`.
|
|
*/
|
|
var _proto = Dispatcher.prototype;
|
|
_proto.register = function register(callback) {
|
|
var id = _prefix + this._lastID++;
|
|
this._callbacks[id] = callback;
|
|
return id;
|
|
}
|
|
|
|
/**
|
|
* Removes a callback based on its token.
|
|
*/;
|
|
_proto.unregister = function unregister(id) {
|
|
!this._callbacks[id] ? process.env.NODE_ENV !== "production" ? invariant(false, 'Dispatcher.unregister(...): `%s` does not map to a registered callback.', id) : invariant(false) : void 0;
|
|
delete this._callbacks[id];
|
|
}
|
|
|
|
/**
|
|
* Waits for the callbacks specified to be invoked before continuing execution
|
|
* of the current callback. This method should only be used by a callback in
|
|
* response to a dispatched payload.
|
|
*/;
|
|
_proto.waitFor = function waitFor(ids) {
|
|
!this._isDispatching ? process.env.NODE_ENV !== "production" ? invariant(false, 'Dispatcher.waitFor(...): Must be invoked while dispatching.') : invariant(false) : void 0;
|
|
for (var ii = 0; ii < ids.length; ii++) {
|
|
var id = ids[ii];
|
|
if (this._isPending[id]) {
|
|
!this._isHandled[id] ? process.env.NODE_ENV !== "production" ? invariant(false, 'Dispatcher.waitFor(...): Circular dependency detected while ' + 'waiting for `%s`.', id) : invariant(false) : void 0;
|
|
continue;
|
|
}
|
|
!this._callbacks[id] ? process.env.NODE_ENV !== "production" ? invariant(false, 'Dispatcher.waitFor(...): `%s` does not map to a registered callback.', id) : invariant(false) : void 0;
|
|
this._invokeCallback(id);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dispatches a payload to all registered callbacks.
|
|
*/;
|
|
_proto.dispatch = function dispatch(payload) {
|
|
!!this._isDispatching ? process.env.NODE_ENV !== "production" ? invariant(false, 'Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch.') : invariant(false) : void 0;
|
|
this._startDispatching(payload);
|
|
try {
|
|
for (var id in this._callbacks) {
|
|
if (this._isPending[id]) {
|
|
continue;
|
|
}
|
|
this._invokeCallback(id);
|
|
}
|
|
} finally {
|
|
this._stopDispatching();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Is this Dispatcher currently dispatching.
|
|
*/;
|
|
_proto.isDispatching = function isDispatching() {
|
|
return this._isDispatching;
|
|
}
|
|
|
|
/**
|
|
* Call the callback stored with the given id. Also do some internal
|
|
* bookkeeping.
|
|
*
|
|
* @internal
|
|
*/;
|
|
_proto._invokeCallback = function _invokeCallback(id) {
|
|
this._isPending[id] = true;
|
|
this._callbacks[id](this._pendingPayload);
|
|
this._isHandled[id] = true;
|
|
}
|
|
|
|
/**
|
|
* Set up bookkeeping needed when dispatching.
|
|
*
|
|
* @internal
|
|
*/;
|
|
_proto._startDispatching = function _startDispatching(payload) {
|
|
for (var id in this._callbacks) {
|
|
this._isPending[id] = false;
|
|
this._isHandled[id] = false;
|
|
}
|
|
this._pendingPayload = payload;
|
|
this._isDispatching = true;
|
|
}
|
|
|
|
/**
|
|
* Clear bookkeeping used for dispatching.
|
|
*
|
|
* @internal
|
|
*/;
|
|
_proto._stopDispatching = function _stopDispatching() {
|
|
delete this._pendingPayload;
|
|
this._isDispatching = false;
|
|
};
|
|
return Dispatcher;
|
|
}();
|
|
module.exports = Dispatcher; |