Skip to content

Commit 8a45d04

Browse files
csvnchristopherthielen
authored andcommitted
fix(onBefore): Skip remaining hooks after the (#2)
first hook that modifies the transition.
1 parent 1c77551 commit 8a45d04

File tree

3 files changed

+113
-15
lines changed

3 files changed

+113
-15
lines changed

src/transition/transition.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,7 @@ export class Transition implements IHookRegistry {
473473
*/
474474
run(): Promise<any> {
475475
let runSynchronousHooks = TransitionHook.runSynchronousHooks;
476+
let runAllHooks = TransitionHook.runAllHooks;
476477
let hookBuilder = this.hookBuilder();
477478
let globals = <Globals> this.router.globals;
478479
globals.transitionHistory.enqueue(this);
@@ -503,15 +504,15 @@ export class Transition implements IHookRegistry {
503504
trace.traceSuccess(this.$to(), this);
504505
this.success = true;
505506
this._deferred.resolve(this.to());
506-
runSynchronousHooks(hookBuilder.getOnSuccessHooks(), true);
507+
runAllHooks(hookBuilder.getOnSuccessHooks());
507508
};
508509

509510
const transitionError = (reason: any) => {
510511
trace.traceError(reason, this);
511512
this.success = false;
512513
this._deferred.reject(reason);
513514
this._error = reason;
514-
runSynchronousHooks(hookBuilder.getOnErrorHooks(), true);
515+
runAllHooks(hookBuilder.getOnErrorHooks());
515516
};
516517

517518
trace.traceTransitionStart(this);

src/transition/transitionHook.ts

+25-12
Original file line numberDiff line numberDiff line change
@@ -94,31 +94,44 @@ export class TransitionHook {
9494
return `${event} context: ${context}, ${maxLength(200, name)}`;
9595
}
9696

97+
/**
98+
* Run all TransitionHooks, ignoring their return value.
99+
*/
100+
static runAllHooks(hooks: TransitionHook[]): void {
101+
hooks.forEach(hook => {
102+
try {
103+
hook.invokeHook();
104+
} catch (exception) {
105+
let errorHandler = hook.transition.router.stateService.defaultErrorHandler();
106+
errorHandler(exception);
107+
}
108+
});
109+
}
97110

98111
/**
99112
* Given an array of TransitionHooks, runs each one synchronously and sequentially.
113+
* Should any hook return a Rejection synchronously, the remaining hooks will not run.
100114
*
101115
* Returns a promise chain composed of any promises returned from each hook.invokeStep() call
102116
*/
103-
static runSynchronousHooks(hooks: TransitionHook[], swallowExceptions: boolean = false): Promise<any> {
117+
static runSynchronousHooks(hooks: TransitionHook[]): Promise<any> {
104118
let results: Promise<HookResult>[] = [];
105-
for (let i = 0; i < hooks.length; i++) {
106-
let hook = hooks[i];
119+
120+
for (let hook of hooks) {
107121
try {
108-
results.push(hook.invokeHook());
109-
} catch (exception) {
110-
if (!swallowExceptions) {
111-
return Rejection.errored(exception).toPromise();
122+
let hookResult = hook.invokeHook();
123+
124+
if (Rejection.isTransitionRejectionPromise(hookResult)) {
125+
// Break on first thrown error or false/TargetState
126+
return hookResult;
112127
}
113128

114-
let errorHandler = hook.transition.router.stateService.defaultErrorHandler();
115-
errorHandler(exception);
129+
results.push(hookResult);
130+
} catch (exception) {
131+
return Rejection.errored(exception).toPromise();
116132
}
117133
}
118134

119-
let rejections = results.filter(Rejection.isTransitionRejectionPromise);
120-
if (rejections.length) return rejections[0];
121-
122135
return results
123136
.filter(isPromise)
124137
.reduce((chain: Promise<any>, promise: Promise<any>) => chain.then(val(promise)), services.$q.when());

test/transitionSpec.ts

+85-1
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,61 @@ describe('transition', function () {
152152
}), 20);
153153
}));
154154

155+
describe('.onBefore()', function() {
156+
beforeEach(() => $state.defaultErrorHandler(() => {}));
157+
158+
it('should stop running remaining hooks if hook modifies transition synchronously', ((done) => {
159+
let counter = 0;
160+
let increment = (amount) => {
161+
return () => {
162+
counter += amount;
163+
return false;
164+
};
165+
};
166+
167+
$transitions.onBefore({}, increment(1), { priority: 2 });
168+
$transitions.onBefore({}, increment(100), { priority: 1 });
169+
170+
Promise.resolve()
171+
.then(goFail('first', 'second'))
172+
.then(() => expect(counter).toBe(1))
173+
.then(goFail('second', 'third'))
174+
.then(() => expect(counter).toBe(2))
175+
.then(done);
176+
}));
177+
178+
it('should stop running remaining hooks when synchronous result throws or returns false|TargetState', ((done) => {
179+
let current = null;
180+
181+
$transitions.onBefore({}, (t) => { current = t.to().name; });
182+
$transitions.onBefore({ to: 'first' }, () => {
183+
throw Error('first-error');
184+
}, { priority: 1 });
185+
$transitions.onBefore({ to: 'second' }, () => false, { priority: 3 });
186+
$transitions.onBefore({ to: 'third' }, () => $state.target('A'), { priority: 2 });
187+
188+
Promise.resolve()
189+
.then(goFail('A', 'first'))
190+
.then((res) => {
191+
expect(res.type).toBe(RejectType.ERROR);
192+
expect(current).toBe(null);
193+
})
194+
.then(goFail('A', 'second'))
195+
.then((res) => {
196+
expect(res.type).toBe(RejectType.ABORTED);
197+
expect(current).toBe(null);
198+
})
199+
.then(goFail('A', 'third'))
200+
.then((res) => {
201+
expect(res.type).toBe(RejectType.SUPERSEDED);
202+
expect(current).toBe(null);
203+
})
204+
.then(go('A', 'B'))
205+
.then(() => expect(current).toBe('B'))
206+
.then(done);
207+
}));
208+
});
209+
155210
describe('.onStart()', function() {
156211
it('should fire matching events when transition starts', ((done) => {
157212
var t = null;
@@ -317,7 +372,9 @@ describe('transition', function () {
317372
.then(done);
318373
}));
319374

320-
it('should be called even if other .onSuccess() callbacks fail (throw errors, etc)', ((done) => {
375+
it('should call all .onSuccess() even when callbacks fail (throw errors, etc)', ((done) => {
376+
$transitions.onSuccess({ from: "*", to: "*" }, () => false);
377+
$transitions.onSuccess({ from: "*", to: "*" }, () => $state.target('A'));
321378
$transitions.onSuccess({ from: "*", to: "*" }, function() { throw new Error("oops!"); });
322379
$transitions.onSuccess({ from: "*", to: "*" }, function(trans) { states.push(trans.to().name); });
323380

@@ -364,6 +421,33 @@ describe('transition', function () {
364421
.then(() => expect(hooks).toEqual([ 'splatsplat', 'AD' ]))
365422
.then(done);
366423
}));
424+
425+
it('should call all error handlers when transition fails.', ((done) => {
426+
let count = 0;
427+
428+
$state.defaultErrorHandler(() => {});
429+
$transitions.onStart({}, () => false);
430+
$transitions.onError({}, () => {
431+
count += 1;
432+
return false;
433+
});
434+
$transitions.onError({}, () => {
435+
count += 10;
436+
$state.target('A');
437+
});
438+
$transitions.onError({}, function() {
439+
count += 100;
440+
throw new Error("oops!");
441+
});
442+
$transitions.onError({}, () => {
443+
count += 1000;
444+
});
445+
446+
Promise.resolve()
447+
.then(goFail("B", "C"))
448+
.then(() => expect(count).toBe(1111))
449+
.then(done);
450+
}));
367451
});
368452

369453
// Test for #2866

0 commit comments

Comments
 (0)