@@ -2,13 +2,13 @@ var React = require('react');
2
2
var warning = require ( 'react/lib/warning' ) ;
3
3
var copyProperties = require ( 'react/lib/copyProperties' ) ;
4
4
var canUseDOM = require ( 'react/lib/ExecutionEnvironment' ) . canUseDOM ;
5
- var Promise = require ( 'when/lib/Promise' ) ;
6
5
var LocationActions = require ( '../actions/LocationActions' ) ;
7
6
var Route = require ( '../components/Route' ) ;
8
7
var ActiveDelegate = require ( '../mixins/ActiveDelegate' ) ;
9
8
var PathListener = require ( '../mixins/PathListener' ) ;
10
9
var RouteStore = require ( '../stores/RouteStore' ) ;
11
10
var Path = require ( '../utils/Path' ) ;
11
+ var Promise = require ( '../utils/Promise' ) ;
12
12
var Redirect = require ( '../utils/Redirect' ) ;
13
13
var Transition = require ( '../utils/Transition' ) ;
14
14
@@ -40,9 +40,7 @@ function defaultAbortedTransitionHandler(transition) {
40
40
* error so that it isn't silently swallowed.
41
41
*/
42
42
function 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.
46
44
}
47
45
48
46
/**
@@ -119,47 +117,50 @@ var Routes = React.createClass({
119
117
return findMatches ( Path . withoutQuery ( path ) , this . state . routes , this . props . defaultRoute , this . props . notFoundRoute ) ;
120
118
} ,
121
119
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
+
122
135
/**
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.
125
139
*
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.
133
146
*
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).
137
149
*
138
150
* 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.
141
151
*/
142
- updatePath : function ( path ) {
143
- var routes = this ;
152
+ dispatch : function ( path , callback ) {
144
153
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 ) ;
145
159
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
+ } ) ;
163
164
} ,
164
165
165
166
render : function ( ) {
@@ -248,14 +249,13 @@ function updateMatchComponents(matches, refs) {
248
249
}
249
250
250
251
/**
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.
255
255
*/
256
- function runTransitionHooks ( routes , transition ) {
256
+ function computeNextState ( routes , transition , callback ) {
257
257
if ( routes . state . path === transition . path )
258
- return Promise . resolve ( ) ; // Nothing to do!
258
+ return callback ( ) ; // Nothing to do!
259
259
260
260
var currentMatches = routes . state . matches ;
261
261
var nextMatches = routes . match ( transition . path ) ;
@@ -287,26 +287,26 @@ function runTransitionHooks(routes, transition) {
287
287
288
288
var query = Path . extractQuery ( transition . path ) || { } ;
289
289
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 ) ;
293
293
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 ) ;
297
297
298
298
var rootMatch = getRootMatch ( nextMatches ) ;
299
299
var params = ( rootMatch && rootMatch . params ) || { } ;
300
300
301
- return {
301
+ callback ( null , {
302
302
path : transition . path ,
303
303
matches : nextMatches ,
304
304
activeParams : params ,
305
305
activeQuery : query ,
306
306
activeRoutes : nextMatches . map ( function ( match ) {
307
307
return match . route ;
308
308
} )
309
- } ;
309
+ } ) ;
310
310
} ) ;
311
311
} ) ;
312
312
}
@@ -315,41 +315,76 @@ function runTransitionHooks(routes, transition) {
315
315
* Calls the willTransitionFrom hook of all handlers in the given matches
316
316
* serially in reverse with the transition object and the current instance of
317
317
* 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 .
319
319
*/
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 ( ) {
325
323
var handler = match . route . props . handler ;
326
324
327
325
if ( ! transition . isAborted && handler . willTransitionFrom )
328
326
return handler . willTransitionFrom ( transition , match . component ) ;
329
- } ) ;
327
+
328
+ var promise = transition . promise ;
329
+ delete transition . promise ;
330
+
331
+ return promise ;
332
+ } ;
330
333
} ) ;
331
334
332
- return promise ;
335
+ runHooks ( hooks , callback ) ;
333
336
}
334
337
335
338
/**
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 .
339
342
*/
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 ( ) {
345
346
var handler = match . route . props . handler ;
346
347
347
348
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
+ } ;
350
356
} ) ;
351
357
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
+ }
353
388
}
354
389
355
390
/**
0 commit comments