Skip to content

Commit

Permalink
add aborted and isNavigationFailure (vuejs#184)
Browse files Browse the repository at this point in the history
  • Loading branch information
posva committed Jul 28, 2020
1 parent 3f9eebb commit 33c48c5
Showing 1 changed file with 40 additions and 17 deletions.
57 changes: 40 additions & 17 deletions active-rfcs/0033-router-navigation-failures.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ router.beforeEach((to, from, next) => {
Then the promise returned by `router.push` rejects:

```js
router.push('/url').catch(err => {
router.push('/url').catch((err) => {
// ...
})
```

In **all** other cases, the promise is resolved. We can know if the navigation failed or not by checking the resolved value:

```js
router.push('/dashboard').then(failure => {
router.push('/dashboard').then((failure) => {
if (failure) {
failure instanceof Error // true
failure.type // NavigationFailure.canceled
Expand Down Expand Up @@ -83,16 +83,16 @@ Navigation methods like `push` return a Promise, this promise **resolves** once
To differentiate a Navigation that succeeded from one that failed, the `Promise` returned by `push` will resolve to either `undefined` or a `NavigationFailure`:

```js
import { NavigationFailureType } from 'vue-router'
import { NavigationFailureType, isNavigationFailure } from 'vue-router'

router.push('/').then(failure => {
router.push('/').then((failure) => {
if (failure) {
// Having an Error instance allows us to have a Stacktrace and trace back
// where the navigation was cancelled. This will, in many cases, lead to a
// Navigation Guard and the corresponding `next()` call that cancelled the
// navigation
failure instanceof Error // true
if (failure.type === NavigationFailureType.canceled) {
if (isNavigationFailure(failure, NavigationFailureType.canceled)) {
// ...
}
}
Expand All @@ -111,9 +111,10 @@ By not rejecting the `Promise` when the navigation fails, we are avoiding _Uncau

There are a few different navigation failures, to be able to react differently in your code

Navigation failures can be differentiated through a `type` property. All possible values are hold by an `enum`, `NavigationFailureType`:
Navigation failures can be differentiated through a `type` property **although you don't need to directly check it**. All possible values are hold by an `enum`, `NavigationFailureType`:

- `cancelled`: `next(false)` inside of a navigation guard or a newer navigation took place while another one was ongoing.
- `aborted`: a newer navigation took place while the current one was ongoing.
- `cancelled`: `next(false)` inside of a navigation guard.
- `duplicated`: Navigating to the same location as the current one will cancel the navigation and not invoke any Navigation guard.

On top of the `type` property, navigation failures also expose `from` and `to` properties, exactly like `router.afterEach`
Expand Down Expand Up @@ -148,27 +149,49 @@ router.currentRoute // { path: '/login', redirectedFrom: { path: '/profile/dashb
Since `router.afterEach` also triggers when a navigation fails, we need a way to know if the navigation succeeded or failed. To do that, we introduce an extra parameter that contains the same _failure_ we could find in a resolved navigation:

```js
import { NavigationFailureType } from 'vue-router'
import { NavigationFailureType, isNavigationFailure } from 'vue-router'

router.afterEach((to, from, failure) => {
if (failure) {
if (failure.type === NavigationFailureType.canceled) {
// ...
}
if (isNavigationFailure(failure)) {
// ...
}
})
```

# Drawbacks
## Differentiating Navigation failures

- Breaking change although migration is relatively simple and in many cases will allow the developer to remove existing code
Instead of directly checking the `type` property, we can use the `isNavigationFailure` helper:

```js
import { NavigationFailureType, isNavigationFailure } from 'vue-router'

router.afterEach((to, from, failure) => {
// Any kind of navigation failure
if (isNavigationFailure(failure)) {
// ...
}
// Only duplicated navigations
if (isNavigationFailure(failure, NavigationFailureType.duplicated)) {
// ...
}
// Aborted or canceled navigations
if (
isNavigationFailure(
failure,
NavigationFailureType.aborted | NavigationFailureType.canceled
)
) {
// ...
}
})
```

# Alternatives
# Drawbacks

- Differentiating `next(false)` from navigations that get overridden by more recent navigations by defining another Navigation Failure
- Breaking change although migration is relatively simple and in many cases will allow the developer to remove existing code

# Adoption strategy

- Expose `NavigationFailureType` in vue-router@3 so that Navigation Failures can be told apart from regular Errors. We could also expose a function `isNavigationFailure` to tell them apart.
- Expose `NavigationFailureType` and `isNavigationFailure` in vue-router@3 so that Navigation Failures can be told apart from regular Errors.
- `afterEach` and `onError` are relatively simple to migrate, most of the time they are not used many times either.
- `router.push` doesn't reject when navigation fails anymore. Any code relying on catching an error should await the promise result instead.

0 comments on commit 33c48c5

Please sign in to comment.