question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

No stacktrace on NavigationDuplicated error uncaught in promise

See original GitHub issue

Version

3.1.1

Reproduction link

https://jsfiddle.net/Lom7hxn5/

Steps to reproduce

  1. Call $router.replace or $router.push twice with same path

What is expected?

Error object provides stacktrace (stack property)

What is actually happening?

No stack trace in the error object


When NavigationDuplicated error is thrown, there is no stack trace in the error itself so when error is reported through error reporting service like Sentry, there is absolutely no context to go with the error and so it’s hard to figure out where that error originated.

Transpiled code for the NavigationDuplicated class looks like this: https://github.com/vuejs/vue-router/blob/5141def3a21da479c2858c0d70db3cb438c3b7d0/dist/vue-router.common.js#L1935-L1946

which is problematic because instantiating such function won’t create a stacktrace. It would work fine with non-transpiled code (although I’m not suggesting to not transpile it): https://github.com/vuejs/vue-router/blob/44c63a963f05ede229d8658a45b2388e244ce00b/src/history/errors.js#L1

One possible solution would be to manually set stack trace with:

this.stack = (new Error()).stack;

but I’m not sure how that would be done if transpilation is done by webpack/babel…

There is also one extra bug in that code. The line that instantiates error passes a route object to the error: https://github.com/vuejs/vue-router/blob/fc42d9cf8e1d381b885c9af37003198dce6f1161/src/history/base.js#L125 but error doesn’t do anything with it: https://github.com/vuejs/vue-router/blob/44c63a963f05ede229d8658a45b2388e244ce00b/src/history/errors.js#L2

Saving route properties on the error object would also help in deciphering source of the error.

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:25
  • Comments:35 (15 by maintainers)

github_iconTop GitHub Comments

208reactions
posvacommented, Nov 10, 2020

I understand it may be a bit confusing to see the UncaughRejection error in the console, so let me summarize what it’s all about:

The error you see in the console is part of the new promise api: before, if no callbacks were supplied to router.push, errors were only sent to the global router error handler. Now, because both push and replace return a promise, if the navigation failure (anything that cancels a navigation like a next(false) or next('/other') also counts) is not caught, you will see an error in the console because that promise rejection is not caught. However, the failure was always there because trying to navigate to same location as the current one fails. It’s now visible because of the promise being rejected but not caught.

This behavior doesn’t happen with router-link: no errors are emitted (except globally). This is because it’s catching the error by adding a noop callback, so you don’t need to catch anything when using router-link. You can catch the rejection when doing programmatic navigation and ignore it:

router.push('/location', () => {})

But if you are using router.push in your code and you don’t care about the navigation failing, you should catch it by using catch:

router.push('/location').catch(err => {})

The last version makes more sense as the Promise api is likely to become the default and the callback version to become deprecated.

This isn’t a breaking change because the code still works the same it was before. The only exceptions are if you were doing one of these:

return router.push('/location') // this is now returning the promise
await router.push('/location') // this explicitly awaits for the navigation to be finished

However, router.push never returned anything before 3.1, so both cases were invalid usage. Even worse, if you were waiting for the resolution of router.push, it never worked because the function wasn’t returning a promise, so the navigation wasn’t even resolved nor failed after calling push. Now that it returns a Promise, it is possibly exposing bugs that were not visible before.

If you want to handle this globally, you can replace Router’s push/replace functions to silence the rejection and make the promise resolve with the error instead:

import Router from 'vue-router'

const originalPush = Router.prototype.push
Router.prototype.push = function push(location, onResolve, onReject) {
  if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject)
  return originalPush.call(this, location).catch(err => err)
}

I discourage from swallowing the errors like this because doing await router.push() will always resolve even when the navigation fails.

With the new navigation failures, you can replicate vue-router-next behavior:

import Router from 'vue-router'

const originalPush = Router.prototype.push
Router.prototype.push = function push(location, onResolve, onReject) {
  if (onResolve || onReject)
    return originalPush.call(this, location, onResolve, onReject)
  return originalPush.call(this, location).catch((err) => {
    if (Router.isNavigationFailure(err)) {
      // resolve err
      return err
    }
    // rethrow error
    return Promise.reject(err)
  })
}

This was reported already at https://github.com/vuejs/vue-router/issues/2872 and https://github.com/vuejs/vue-router/issues/2873. This issue is about improving the stack trace of the Rejection so if you could keep the conversation about that, it would help me a lot! I hope that this answer, being more detailed brings clarity to the topic. I’ve also been looking at how this impacts Nuxt with their core team so the error doesn’t appear if it shouldn’t.

35reactions
shayneocommented, Aug 12, 2019

hey @posva thanks so much for the thorough breakdown. I think the confusion is coming into play regarding what you described when you said:

“However, the failure was always there because trying to navigate to same location as the current one fails. It’s now visible because of the promise being rejected but not caught.”

While I understand that the above is true, I don’t think it was unreasonable for anyone to assume that the router’s default behavior would be to handle the NavigationDuplicated usecase “out of the box”… I’ve been using vue-router for several years, and I thought this was just default functionality all along.

I’m absolutely a fan of the promise API and see some immediate benefits, but I’ve had to rollback vue-router to a pre 3.1 version because I don’t have a way of cleaning up the errors that are being thrown without adding catch to all of my router.push’s.

Perhaps we can spin up a separate “feature request” issue for handling NavigationDuplicated out of the box?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Got Uncaught (in promise) NavigationDuplicated error on ...
Hello! In my Laravel 5.8 / “vue”: “^2.6.10”/ “vuex”: “^3.1.0” app In resources/js/components/Login.vue file I have method methods: ...
Read more >
Got Uncaught (in promise) NavigationDuplicated error on ...
I checked, that there is no any this.$router.push command to attempt redirect back to the login page, asyopu suppose... Can I set some...
Read more >
uncaught (in promise) error: request aborted - You.com
Intended outcome: no error. Actual outcome: in chrome console, always throw error: Uncaught (in promise) DOMException: The user aborted a request.
Read more >
Got Uncaught (in promise) NavigationDuplicated ... - Laracasts
Hello, In laravel 5 / vuejs 2.6 app I have login form and on login failure I got error : app.js?dt=1571914585:131483 Uncaught (in...
Read more >
Web IDE Error When Selecting File After Creating New File
Stack Trace: vue-router.esm.js:2008 Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation to current location: ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found