forked from facebook/react-native
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[flux-todomvc] Replace dispatcher with new one
- Loading branch information
1 parent
a511d2f
commit 1ba5beb
Showing
3 changed files
with
277 additions
and
89 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,113 +1,248 @@ | ||
/* | ||
* Copyright (c) 2014, Facebook, Inc. | ||
* 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 | ||
* @typechecks | ||
*/ | ||
|
||
var invariant = require('./invariant'); | ||
|
||
var _lastID = 1; | ||
var _prefix = 'ID_'; | ||
|
||
/** | ||
* Copyright 2013-2014 Facebook, Inc. | ||
* 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. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* // Select the default city for the new country | ||
* CityStore.city = getDefaultCityForCountry(CountryStore.country); | ||
* } | ||
* }); | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* The usage of `waitFor()` can be chained, for example: | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* FlightPriceStore.dispatchToken = | ||
* flightDispatcher.register(function(payload)) { | ||
* switch (payload.actionType) { | ||
* case 'country-update': | ||
* flightDispatcher.waitFor([CityStore.dispatchToken]); | ||
* FlightPriceStore.price = | ||
* getFlightPriceStore(CountryStore.country, CityStore.city); | ||
* break; | ||
* | ||
* Dispatcher | ||
* case 'city-update': | ||
* FlightPriceStore.price = | ||
* FlightPriceStore(CountryStore.country, CityStore.city); | ||
* break; | ||
* } | ||
* }); | ||
* | ||
* The Dispatcher is capable of registering callbacks and invoking them. | ||
* More robust implementations than this would include a way to order the | ||
* callbacks for dependent Stores, and to guarantee that no two stores | ||
* created circular dependencies. | ||
* The `country-update` payload will be guaranteed to invoke the stores' | ||
* registered callbacks in order: `CountryStore`, `CityStore`, then | ||
* `FlightPriceStore`. | ||
*/ | ||
|
||
var Promise = require('es6-promise').Promise; | ||
var merge = require('react/lib/merge'); | ||
|
||
var _callbacks = []; | ||
var _promises = []; | ||
|
||
var Dispatcher = function() {}; | ||
Dispatcher.prototype = merge(Dispatcher.prototype, { | ||
function Dispatcher() {"use strict"; | ||
this.$Dispatcher_callbacks = {}; | ||
this.$Dispatcher_isPending = {}; | ||
this.$Dispatcher_isHandled = {}; | ||
this.$Dispatcher_isDispatching = false; | ||
this.$Dispatcher_pendingPayload = null; | ||
} | ||
|
||
/** | ||
* Register a Store's callback so that it may be invoked by an action. | ||
* @param {function} callback The callback to be registered. | ||
* @return {number} The index of the callback within the _callbacks array. | ||
* Registers a callback to be invoked with every dispatched payload. Returns | ||
* a token that can be used with `waitFor()`. | ||
* | ||
* @param {function} callback | ||
* @return {string} | ||
*/ | ||
register: function(callback) { | ||
_callbacks.push(callback); | ||
return _callbacks.length - 1; // index | ||
}, | ||
Dispatcher.prototype.register=function(callback) {"use strict"; | ||
var id = _prefix + _lastID++; | ||
this.$Dispatcher_callbacks[id] = callback; | ||
return id; | ||
}; | ||
|
||
/** | ||
* dispatch | ||
* @param {object} payload The data from the action. | ||
* Removes a callback based on its token. | ||
* | ||
* @param {string} id | ||
*/ | ||
dispatch: function(payload) { | ||
// First create array of promises for callbacks to reference. | ||
var resolves = []; | ||
var rejects = []; | ||
_promises = _callbacks.map(function(_, i) { | ||
return new Promise(function(resolve, reject) { | ||
resolves[i] = resolve; | ||
rejects[i] = reject; | ||
}); | ||
}); | ||
// Dispatch to callbacks and resolve/reject promises. | ||
_callbacks.forEach(function(callback, i) { | ||
// Callback can return an obj, to resolve, or a promise, to chain. | ||
// See waitFor() for why this might be useful. | ||
Promise.resolve(callback(payload)).then(function() { | ||
resolves[i](payload); | ||
}, function() { | ||
rejects[i](new Error('Dispatcher callback unsuccessful')); | ||
}); | ||
}); | ||
_promises = []; | ||
}, | ||
Dispatcher.prototype.unregister=function(id) {"use strict"; | ||
invariant( | ||
this.$Dispatcher_callbacks[id], | ||
'Dispatcher.unregister(...): `%s` does not map to a registered callback.', | ||
id | ||
); | ||
delete this.$Dispatcher_callbacks[id]; | ||
}; | ||
|
||
/** | ||
* Allows a store to wait for the registered callbacks of other stores | ||
* to get invoked before its own does. | ||
* This function is not used by this TodoMVC example application, but | ||
* it is very useful in a larger, more complex application. | ||
* | ||
* Example usage where StoreB waits for StoreA: | ||
* 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. | ||
* | ||
* var StoreA = merge(EventEmitter.prototype, { | ||
* // other methods omitted | ||
* @param {array<string>} ids | ||
*/ | ||
Dispatcher.prototype.waitFor=function(ids) {"use strict"; | ||
invariant( | ||
this.$Dispatcher_isDispatching, | ||
'Dispatcher.waitFor(...): Must be invoked while dispatching.' | ||
); | ||
for (var ii = 0; ii < ids.length; ii++) { | ||
var id = ids[ii]; | ||
if (this.$Dispatcher_isPending[id]) { | ||
invariant( | ||
this.$Dispatcher_isHandled[id], | ||
'Dispatcher.waitFor(...): Circular dependency detected while ' + | ||
'waiting for `%s`.', | ||
id | ||
); | ||
continue; | ||
} | ||
invariant( | ||
this.$Dispatcher_callbacks[id], | ||
'Dispatcher.waitFor(...): `%s` does not map to a registered callback.', | ||
id | ||
); | ||
this.$Dispatcher_invokeCallback(id); | ||
} | ||
}; | ||
|
||
/** | ||
* Dispatches a payload to all registered callbacks. | ||
* | ||
* dispatchIndex: Dispatcher.register(function(payload) { | ||
* // switch statement with lots of cases | ||
* }) | ||
* } | ||
* @param {object} payload | ||
*/ | ||
Dispatcher.prototype.dispatch=function(payload) {"use strict"; | ||
invariant( | ||
!this.$Dispatcher_isDispatching, | ||
'Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch.' | ||
); | ||
this.$Dispatcher_startDispatching(payload); | ||
try { | ||
for (var id in this.$Dispatcher_callbacks) { | ||
if (this.$Dispatcher_isPending[id]) { | ||
continue; | ||
} | ||
this.$Dispatcher_invokeCallback(id); | ||
} | ||
} finally { | ||
this.$Dispatcher_stopDispatching(); | ||
} | ||
}; | ||
|
||
/** | ||
* Is this Dispatcher currently dispatching. | ||
* | ||
* var StoreB = merge(EventEmitter.prototype, { | ||
* // other methods omitted | ||
* @return {boolean} | ||
*/ | ||
Dispatcher.prototype.isDispatching=function() {"use strict"; | ||
return this.$Dispatcher_isDispatching; | ||
}; | ||
|
||
/** | ||
* Call the calback stored with the given id. Also do some internal | ||
* bookkeeping. | ||
* | ||
* dispatchIndex: Dispatcher.register(function(payload) { | ||
* switch(payload.action.actionType) { | ||
* @param {string} id | ||
* @internal | ||
*/ | ||
Dispatcher.prototype.$Dispatcher_invokeCallback=function(id) {"use strict"; | ||
this.$Dispatcher_isPending[id] = true; | ||
this.$Dispatcher_callbacks[id](this.$Dispatcher_pendingPayload); | ||
this.$Dispatcher_isHandled[id] = true; | ||
}; | ||
|
||
/** | ||
* Set up bookkeeping needed when dispatching. | ||
* | ||
* case MyConstants.FOO_ACTION: | ||
* Dispatcher.waitFor([StoreA.dispatchIndex], function() { | ||
* // Do stuff only after StoreA's callback returns. | ||
* }); | ||
* } | ||
* }) | ||
* } | ||
* @param {object} payload | ||
* @internal | ||
*/ | ||
Dispatcher.prototype.$Dispatcher_startDispatching=function(payload) {"use strict"; | ||
for (var id in this.$Dispatcher_callbacks) { | ||
this.$Dispatcher_isPending[id] = false; | ||
this.$Dispatcher_isHandled[id] = false; | ||
} | ||
this.$Dispatcher_pendingPayload = payload; | ||
this.$Dispatcher_isDispatching = true; | ||
}; | ||
|
||
/** | ||
* Clear bookkeeping used for dispatching. | ||
* | ||
* It should be noted that if StoreB waits for StoreA, and StoreA waits for | ||
* StoreB, a circular dependency will occur, but no error will be thrown. | ||
* A more robust Dispatcher would issue a warning in this scenario. | ||
* @internal | ||
*/ | ||
waitFor: function(/*array*/ promiseIndexes, /*function*/ callback) { | ||
var selectedPromises = promiseIndexes.map(function(index) { | ||
return _promises[index]; | ||
}); | ||
return Promise.all(selectedPromises).then(callback); | ||
} | ||
Dispatcher.prototype.$Dispatcher_stopDispatching=function() {"use strict"; | ||
this.$Dispatcher_pendingPayload = null; | ||
this.$Dispatcher_isDispatching = false; | ||
}; | ||
|
||
}); | ||
|
||
module.exports = Dispatcher; |
Oops, something went wrong.