Created
August 21, 2014 19:55
-
-
Save mjackson/06c5c8c53a9e10de6275 to your computer and use it in GitHub Desktop.
An async version of Facebook's flux dispatcher
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
var d = require('d'); | |
var warning = require('react/lib/warning'); | |
var Promise = require('es6-promise').Promise; | |
function isNotNull(object) { | |
return object != null; | |
} | |
function Dispatcher() { | |
this._currentActionName = null; | |
this._handlers = []; | |
this._promises = []; | |
} | |
Object.defineProperties(Dispatcher.prototype, { | |
/** | |
* A mixin that React components can use to send actions through | |
* this dispatcher. | |
* | |
* Example: | |
* | |
* var React = require('react'); | |
* var Dispatcher = require('react-dispatcher'); | |
* var Router = require('react-router'); | |
* | |
* Dispatcher.registerHandler(function (actionName, actionArgs) { | |
* if (actionName === 'login') { | |
* var username = actionArgs[0], password = actionArgs[1]; | |
* | |
* // return a promise that fulfills if the username/password | |
* // are good, rejects if there's a problem | |
* return checkCredentials(username, password); | |
* } | |
* }); | |
* | |
* var Login = React.createClass({ | |
* | |
* mixins: [ Dispatcher.ActionSender ], | |
* | |
* getInitialState: function () { | |
* return { errorMessage: '' }; | |
* }, | |
* | |
* handleLoginError: function (error) { | |
* this.setState({ errorMessage: error.message }); | |
* }, | |
* | |
* handleSubmit: function () { | |
* var username = this.refs.username.getDOMNode().value; | |
* var password = this.refs.password.getDOMNode().value; | |
* | |
* this.sendAction('login', username, password).then(function () { | |
* Router.transitionTo('/home/' + username); | |
* }, this.handleLoginError); | |
* }, | |
* | |
* render: function () { | |
* var error; | |
* if (this.state.errorMessage) | |
* error = <div className="error">{this.state.errorMessage}</div>; | |
* | |
* return ( | |
* {error} | |
* <form onSubmit={this.handleSubmit}> | |
* <input ref="username" type="text" name="username"/> | |
* <input ref="password" type="password" name="password"/> | |
* </form> | |
* ); | |
* } | |
* | |
* }); | |
*/ | |
ActionSender: d.gs(function () { | |
var dispatcher = this; | |
return { | |
sendAction: function () { | |
return dispatcher.sendAction.apply(dispatcher, arguments); | |
} | |
}; | |
}), | |
/** | |
* Registers the given handler function to run when a new action | |
* is dispatched and returns an opaque value that other handlers | |
* can use to wait for the given handler to finish processing an | |
* action. | |
* | |
* Example: | |
* | |
* function myHandler(actionName, actionArgs) { | |
* // return a value or a promise for a value | |
* } | |
* | |
* // save the ID so other handlers can waitFor myHandler | |
* var myHandlerID = Dispatcher.registerHandler(handler); | |
* | |
* Dispatcher.registerHandler(function (actionName, actionArgs) { | |
* return Dispatcher.waitFor(myHandlerID).then(function (value) { | |
* // value is the result from myHandler | |
* }); | |
* }); | |
*/ | |
registerHandler: d(function (handler) { | |
this._handlers.push(handler); | |
return this._handlers.length - 1; | |
}), | |
/** | |
* Creates and sends a new action with the given name and arguments to all | |
* registered handlers. Arguments may be an array or a variable-length | |
* argument list. Handlers will be called with two arguments: 1) the action | |
* name and 2) an array of action arguments. | |
* | |
* Handlers may return a promise if they need to handle the action asynchronously. | |
* Returns a promise that resolves when all handlers are done. | |
* | |
* Note: This function throws if you try to dispatch an action while another | |
* is being dispatched. This does not mean that handlers need to wait for all | |
* async handlers to finish before dispatching another action, only that they | |
* shouldn't dispatch another action in the same turn of the event loop. | |
*/ | |
dispatchAction: d(function (actionName, actionArgs) { | |
if (this._promises.length) | |
throw new Error('Dispatch of "' + this._currentActionName + '" is still in progress'); | |
// Update this._currentActionName, for error messages. | |
this._currentActionName = actionName; | |
if (!Array.isArray(actionArgs)) | |
actionArgs = Array.prototype.slice.call(arguments, 1); | |
// Populate this._promises so handlers can reference others in waitFor. | |
var resolves = [], rejects = []; | |
this._promises = this._handlers.map(function (handler, i) { | |
return new Promise(function (resolve, reject) { | |
resolves[i] = resolve; | |
rejects[i] = reject; | |
}); | |
}); | |
// Dispatch to handlers and resolve/reject promises. | |
var results = this._handlers.map(function (handler, i) { | |
var result = handler.call(this, actionName, actionArgs); | |
// Stores can return a promise if they need to make a network request | |
// or waitFor another store to finish processing an action. | |
Promise.resolve(result).then(resolves[i], rejects[i]); | |
return result; | |
}, this); | |
// Emit a warning if nobody handled the action. | |
warning( | |
results.some(isNotNull), | |
'No handlers handled action: ' + actionName | |
); | |
var allResults = Promise.all(this._promises); | |
// Clear the promises array. Stores that use waitFor must do so immediately. | |
this._promises = []; | |
// Return a promise that resolves when all handlers are done. | |
return allResults; | |
}), | |
waitFor: d(function (handlerIDs) { | |
if (!this._promises.length) | |
throw new Error('Handlers must use waitFor in the first turn of the dispatch cycle'); | |
if (Array.isArray(handlerIDs)) | |
return Promise.all(handlerIDs.map(this._getPromise.bind(this))); | |
return Promise.resolve(this._getPromise(handlerIDs)); | |
}), | |
_getPromise: d(function (index) { | |
if (!this._promises[index]) | |
throw new Error('Unregistered dispatcher id: ' + index); | |
return this._promises[index]; | |
}) | |
}); | |
module.exports = Dispatcher; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment