Skip to content

Commit bc87d9e

Browse files
fix(transition): Fail a transition if a new one has started while resolves are loading
Closes ui-router/react#3 Probably closes #2972
1 parent 2fcc920 commit bc87d9e

File tree

1 file changed

+40
-28
lines changed

1 file changed

+40
-28
lines changed

src/transition/transitionHook.ts

+40-28
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@
22
import {TransitionHookOptions, IEventHook, HookResult} from "./interface";
33
import {defaults, noop} from "../common/common";
44
import {fnToString, maxLength} from "../common/strings";
5-
import {isDefined, isPromise } from "../common/predicates";
6-
import {pattern, val, eq, is, parse } from "../common/hof";
5+
import {isPromise} from "../common/predicates";
6+
import {val, is, parse } from "../common/hof";
77
import {trace} from "../common/trace";
88
import {services} from "../common/coreservices";
99

1010
import {Rejection} from "./rejectFactory";
1111
import {TargetState} from "../state/targetState";
12-
import {ResolveContext} from "../resolve/resolveContext";
1312
import {Transition} from "./transition";
1413
import {State} from "../state/stateObject";
1514

@@ -31,7 +30,8 @@ export class TransitionHook {
3130
this.options = defaults(options, defaultOptions);
3231
}
3332

34-
private isSuperseded = () => this.options.current() !== this.options.transition;
33+
private isSuperseded = () =>
34+
this.options.current() !== this.options.transition;
3535

3636
invokeHook(): Promise<HookResult> {
3737
let { options, eventHook } = this;
@@ -40,38 +40,50 @@ export class TransitionHook {
4040
return Rejection.superseded(options.current()).toPromise();
4141
}
4242

43-
let hookResult = !eventHook._deregistered
43+
let synchronousHookResult = !eventHook._deregistered
4444
? eventHook.callback.call(options.bind, this.transition, this.stateContext)
4545
: undefined;
46-
return this.handleHookResult(hookResult);
46+
47+
return this.handleHookResult(synchronousHookResult);
4748
}
4849

4950
/**
5051
* This method handles the return value of a Transition Hook.
5152
*
52-
* A hook can return false, a redirect (TargetState), or a promise (which may resolve to false or a redirect)
53+
* A hook can return false (cancel), a TargetState (redirect),
54+
* or a promise (which may later resolve to false or a redirect)
55+
*
56+
* This also handles "transition superseded" -- when a new transition
57+
* was started while the hook was still running
5358
*/
54-
handleHookResult(hookResult: HookResult): Promise<any> {
55-
if (!isDefined(hookResult)) return undefined;
56-
57-
/**
58-
* Handles transition superseded, transition aborted and transition redirect.
59-
*/
60-
const mapHookResult = pattern([
61-
// Transition is no longer current
62-
[this.isSuperseded, () => Rejection.superseded(this.options.current()).toPromise()],
63-
// If the hook returns false, abort the current Transition
64-
[eq(false), () => Rejection.aborted("Hook aborted transition").toPromise()],
65-
// If the hook returns a Transition, halt the current Transition and redirect to that Transition.
66-
[is(TargetState), (target: TargetState) => Rejection.redirected(target).toPromise()],
67-
// A promise was returned, wait for the promise and then chain another hookHandler
68-
[isPromise, (promise: Promise<any>) => promise.then(this.handleHookResult.bind(this))]
69-
]);
70-
71-
let transitionResult = mapHookResult(hookResult);
72-
if (transitionResult) trace.traceHookResult(hookResult, transitionResult, this.options);
73-
74-
return transitionResult;
59+
handleHookResult(result: HookResult): Promise<any> {
60+
// This transition is no longer current.
61+
// Another transition started while this hook was still running.
62+
if (this.isSuperseded()) {
63+
// Abort this transition
64+
return Rejection.superseded(this.options.current()).toPromise();
65+
}
66+
67+
// Hook returned a promise
68+
if (isPromise(result)) {
69+
// Wait for the promise, then reprocess the resolved value
70+
return result.then(this.handleHookResult.bind(this));
71+
}
72+
73+
trace.traceHookResult(result, this.options);
74+
75+
// Hook returned false
76+
if (result === false) {
77+
// Abort this Transition
78+
return Rejection.aborted("Hook aborted transition").toPromise();
79+
}
80+
81+
const isTargetState = is(TargetState);
82+
// hook returned a TargetState
83+
if (isTargetState(result)) {
84+
// Halt the current Transition and start a redirected Transition (to the TargetState).
85+
return Rejection.redirected(result).toPromise();
86+
}
7587
}
7688

7789
toString() {

0 commit comments

Comments
 (0)