@@ -2,13 +2,13 @@ var React = require('react');
22var warning = require ( 'react/lib/warning' ) ;
33var copyProperties = require ( 'react/lib/copyProperties' ) ;
44var canUseDOM = require ( 'react/lib/ExecutionEnvironment' ) . canUseDOM ;
5- var Promise = require ( 'when/lib/Promise' ) ;
65var LocationActions = require ( '../actions/LocationActions' ) ;
76var Route = require ( '../components/Route' ) ;
87var ActiveDelegate = require ( '../mixins/ActiveDelegate' ) ;
98var PathListener = require ( '../mixins/PathListener' ) ;
109var RouteStore = require ( '../stores/RouteStore' ) ;
1110var Path = require ( '../utils/Path' ) ;
11+ var Promise = require ( '../utils/Promise' ) ;
1212var Redirect = require ( '../utils/Redirect' ) ;
1313var Transition = require ( '../utils/Transition' ) ;
1414
@@ -40,9 +40,7 @@ function defaultAbortedTransitionHandler(transition) {
4040 * error so that it isn't silently swallowed.
4141 */
4242function defaultTransitionErrorHandler ( error ) {
43- setTimeout ( function ( ) { // Use setTimeout to break the promise chain.
44- throw error ; // This error probably originated in a transition hook.
45- } ) ;
43+ throw error ; // This error probably originated in a transition hook.
4644}
4745
4846/**
@@ -119,47 +117,50 @@ var Routes = React.createClass({
119117 return findMatches ( Path . withoutQuery ( path ) , this . state . routes , this . props . defaultRoute , this . props . notFoundRoute ) ;
120118 } ,
121119
120+ updatePath : function ( path ) {
121+ var self = this ;
122+
123+ this . dispatch ( path , function ( error , transition ) {
124+ if ( error ) {
125+ self . props . onTransitionError ( error ) ;
126+ } else if ( transition . isAborted ) {
127+ self . props . onAbortedTransition ( transition ) ;
128+ } else {
129+ self . emitChange ( ) ;
130+ maybeUpdateScroll ( self ) ;
131+ }
132+ } ) ;
133+ } ,
134+
122135 /**
123- * Performs a transition to the given path and returns a promise for the
124- * Transition object that was used.
136+ * Performs a transition to the given path and calls callback(error, transition)
137+ * with the Transition object when the transition is finished and the component's
138+ * state has been updated accordingly.
125139 *
126- * In order to do this, the router first determines which routes are involved
127- * in the transition beginning with the current route, up the route tree to
128- * the first parent route that is shared with the destination route, and back
129- * down the tree to the destination route. The willTransitionFrom static
130- * method is invoked on all route handlers we're transitioning away from, in
131- * reverse nesting order. Likewise, the willTransitionTo static method
132- * is invoked on all route handlers we're transitioning to.
140+ * In a transition, the router first determines which routes are involved by
141+ * beginning with the current route, up the route tree to the first parent route
142+ * that is shared with the destination route, and back down the tree to the
143+ * destination route. The willTransitionFrom hook is invoked on all route handlers
144+ * we're transitioning away from, in reverse nesting order. Likewise, the
145+ * willTransitionTo hook is invoked on all route handlers we're transitioning to.
133146 *
134- * Both willTransitionFrom and willTransitionTo hooks may either abort or
135- * redirect the transition. If they need to resolve asynchronously, they may
136- * return a promise.
147+ * Both willTransitionFrom and willTransitionTo hooks may either abort or redirect
148+ * the transition. To resolve asynchronously, they may use transition.wait(promise).
137149 *
138150 * Note: This function does not update the URL in a browser's location bar.
139- * If you want to keep the URL in sync with transitions, use Router.transitionTo,
140- * Router.replaceWith, or Router.goBack instead.
141151 */
142- updatePath : function ( path ) {
143- var routes = this ;
152+ dispatch : function ( path , callback ) {
144153 var transition = new Transition ( path ) ;
154+ var self = this ;
155+
156+ computeNextState ( this , transition , function ( error , nextState ) {
157+ if ( error || nextState == null )
158+ return callback ( error , transition ) ;
145159
146- return runTransitionHooks ( routes , transition )
147- . then ( function ( newState ) {
148- if ( transition . isAborted )
149- routes . props . onAbortedTransition ( transition ) ;
150-
151- if ( newState == null )
152- return transition ;
153-
154- return new Promise ( function ( resolve ) {
155- routes . setState ( newState , function ( ) {
156- routes . emitChange ( ) ;
157- maybeUpdateScroll ( routes ) ;
158- resolve ( transition ) ;
159- } ) ;
160- } ) ;
161- } )
162- . then ( undefined , this . props . onTransitionError ) ;
160+ self . setState ( nextState , function ( ) {
161+ callback ( null , transition ) ;
162+ } ) ;
163+ } ) ;
163164 } ,
164165
165166 render : function ( ) {
@@ -248,14 +249,13 @@ function updateMatchComponents(matches, refs) {
248249}
249250
250251/**
251- * Runs all transition hooks that are required to get from the current state
252- * to the state specified by the given transition and updates the current state
253- * if they all pass successfully. Returns a promise that resolves to the new
254- * state if it needs to be updated, or undefined if not.
252+ * Computes the next state for the given <Routes> component and calls
253+ * callback(error, nextState) when finished. Also runs all transition
254+ * hooks along the way.
255255 */
256- function runTransitionHooks ( routes , transition ) {
256+ function computeNextState ( routes , transition , callback ) {
257257 if ( routes . state . path === transition . path )
258- return Promise . resolve ( ) ; // Nothing to do!
258+ return callback ( ) ; // Nothing to do!
259259
260260 var currentMatches = routes . state . matches ;
261261 var nextMatches = routes . match ( transition . path ) ;
@@ -287,26 +287,26 @@ function runTransitionHooks(routes, transition) {
287287
288288 var query = Path . extractQuery ( transition . path ) || { } ;
289289
290- return runTransitionFromHooks ( fromMatches , transition ) . then ( function ( ) {
291- if ( transition . isAborted )
292- return ; // No need to continue.
290+ runTransitionFromHooks ( fromMatches , transition , function ( error ) {
291+ if ( error || transition . isAborted )
292+ return callback ( error ) ;
293293
294- return runTransitionToHooks ( toMatches , transition , query ) . then ( function ( ) {
295- if ( transition . isAborted )
296- return ; // No need to continue.
294+ runTransitionToHooks ( toMatches , transition , query , function ( error ) {
295+ if ( error || transition . isAborted )
296+ return callback ( error ) ;
297297
298298 var rootMatch = getRootMatch ( nextMatches ) ;
299299 var params = ( rootMatch && rootMatch . params ) || { } ;
300300
301- return {
301+ callback ( null , {
302302 path : transition . path ,
303303 matches : nextMatches ,
304304 activeParams : params ,
305305 activeQuery : query ,
306306 activeRoutes : nextMatches . map ( function ( match ) {
307307 return match . route ;
308308 } )
309- } ;
309+ } ) ;
310310 } ) ;
311311 } ) ;
312312}
@@ -315,41 +315,76 @@ function runTransitionHooks(routes, transition) {
315315 * Calls the willTransitionFrom hook of all handlers in the given matches
316316 * serially in reverse with the transition object and the current instance of
317317 * the route's handler, so that the deepest nested handlers are called first.
318- * Returns a promise that resolves after the last handler .
318+ * Calls callback(error) when finished .
319319 */
320- function runTransitionFromHooks ( matches , transition ) {
321- var promise = Promise . resolve ( ) ;
322-
323- reversedArray ( matches ) . forEach ( function ( match ) {
324- promise = promise . then ( function ( ) {
320+ function runTransitionFromHooks ( matches , transition , callback ) {
321+ var hooks = reversedArray ( matches ) . map ( function ( match ) {
322+ return function ( ) {
325323 var handler = match . route . props . handler ;
326324
327325 if ( ! transition . isAborted && handler . willTransitionFrom )
328326 return handler . willTransitionFrom ( transition , match . component ) ;
329- } ) ;
327+
328+ var promise = transition . promise ;
329+ delete transition . promise ;
330+
331+ return promise ;
332+ } ;
330333 } ) ;
331334
332- return promise ;
335+ runHooks ( hooks , callback ) ;
333336}
334337
335338/**
336- * Calls the willTransitionTo hook of all handlers in the given matches serially
337- * with the transition object and any params that apply to that handler. Returns
338- * a promise that resolves after the last handler .
339+ * Calls the willTransitionTo hook of all handlers in the given matches
340+ * serially with the transition object and any params that apply to that
341+ * handler. Calls callback(error) when finished .
339342 */
340- function runTransitionToHooks ( matches , transition , query ) {
341- var promise = Promise . resolve ( ) ;
342-
343- matches . forEach ( function ( match ) {
344- promise = promise . then ( function ( ) {
343+ function runTransitionToHooks ( matches , transition , query , callback ) {
344+ var hooks = matches . map ( function ( match ) {
345+ return function ( ) {
345346 var handler = match . route . props . handler ;
346347
347348 if ( ! transition . isAborted && handler . willTransitionTo )
348- return handler . willTransitionTo ( transition , match . params , query ) ;
349- } ) ;
349+ handler . willTransitionTo ( transition , match . params , query ) ;
350+
351+ var promise = transition . promise ;
352+ delete transition . promise ;
353+
354+ return promise ;
355+ } ;
350356 } ) ;
351357
352- return promise ;
358+ runHooks ( hooks , callback ) ;
359+ }
360+
361+ /**
362+ * Runs all hook functions serially and calls callback(error) when finished.
363+ * A hook may return a promise if it needs to execute asynchronously.
364+ */
365+ function runHooks ( hooks , callback ) {
366+ try {
367+ var promise = hooks . reduce ( function ( promise , hook ) {
368+ // The first hook to use transition.wait makes the rest
369+ // of the transition async from that point forward.
370+ return promise ? promise . then ( hook ) : hook ( ) ;
371+ } , null ) ;
372+ } catch ( error ) {
373+ return callback ( error ) ; // Sync error.
374+ }
375+
376+ if ( promise ) {
377+ // Use setTimeout to break the promise chain.
378+ promise . then ( function ( ) {
379+ setTimeout ( callback ) ;
380+ } , function ( error ) {
381+ setTimeout ( function ( ) {
382+ callback ( error ) ;
383+ } ) ;
384+ } ) ;
385+ } else {
386+ callback ( ) ;
387+ }
353388}
354389
355390/**
0 commit comments