diff --git a/modules/helpers/resolveAsyncState.js b/modules/helpers/resolveAsyncState.js
new file mode 100644
index 0000000000..97b3d46511
--- /dev/null
+++ b/modules/helpers/resolveAsyncState.js
@@ -0,0 +1,24 @@
+var Promise = require('es6-promise').Promise;
+
+/**
+ * Resolves all values in the given stateDescription object
+ * and calls the setState function with new state as they resolve.
+ */
+function resolveAsyncState(stateDescription, setState) {
+ if (stateDescription == null)
+ return Promise.resolve({});
+
+ var keys = Object.keys(stateDescription);
+
+ return Promise.all(
+ keys.map(function (key) {
+ return Promise.resolve(stateDescription[key]).then(function (value) {
+ var newState = {};
+ newState[key] = value;
+ setState(newState);
+ });
+ })
+ );
+}
+
+module.exports = resolveAsyncState;
diff --git a/modules/main.js b/modules/main.js
index c840e54685..e5421c7371 100644
--- a/modules/main.js
+++ b/modules/main.js
@@ -7,6 +7,7 @@ exports.replaceWith = require('./helpers/replaceWith');
exports.transitionTo = require('./helpers/transitionTo');
exports.ActiveState = require('./mixins/ActiveState');
+exports.AsyncState = require('./mixins/AsyncState');
// Backwards compat with 0.1. We should
// remove this when we ship 1.0.
diff --git a/modules/mixins/AsyncState.js b/modules/mixins/AsyncState.js
new file mode 100644
index 0000000000..748c246115
--- /dev/null
+++ b/modules/mixins/AsyncState.js
@@ -0,0 +1,106 @@
+var React = require('react');
+var resolveAsyncState = require('../helpers/resolveAsyncState');
+
+/**
+ * A mixin for route handler component classes that fetch at least
+ * part of their state asynchronously. Classes that use it should
+ * declare a static `getInitialAsyncState` method that fetches state
+ * for a component after it mounts. This function is given three
+ * arguments: 1) the current route params, 2) the current query and
+ * 3) a function that can be used to set state as it is received.
+ *
+ * Example:
+ *
+ * var User = React.createClass({
+ *
+ * statics: {
+ *
+ * getInitialAsyncState: function (params, query, setState) {
+ * // If you don't need to do anything async, just update
+ * // the state immediately and you're done.
+ * setState({
+ * user: UserStore.getUserByID(params.userID)
+ * });
+ *
+ * // Or, ignore the setState argument entirely and return a
+ * // hash with keys named after the state variables you want
+ * // to set. The values may be immediate values or promises.
+ * return {
+ * user: getUserByID(params.userID) // may be a promise
+ * };
+ *
+ * // Or, stream your data!
+ * var buffer = '';
+ *
+ * return {
+ *
+ * // Same as above, the stream state variable is set to the
+ * // value returned by this promise when it resolves.
+ * stream: getStreamingData(params.userID, function (chunk) {
+ * buffer += chunk;
+ *
+ * // Notify of progress.
+ * setState({
+ * streamBuffer: buffer
+ * });
+ * })
+ *
+ * };
+ * }
+ *
+ * },
+ *
+ * getInitialState: function () {
+ * return {
+ * user: null, // Receives a value when getUserByID resolves.
+ * stream: null, // Receives a value when getStreamingData resolves.
+ * streamBuffer: '' // Used to track data as it loads.
+ * };
+ * },
+ *
+ * render: function () {
+ * if (!this.state.user)
+ * return
Welcome {this.state.user.name}!
+ *So far, you've received {this.state.streamBuffer.length} data!
+ *