Introduction: RECENTLY I encountered a bug when I was making a VUE project: clicking a button in the navigation component repeatedly, the console would report an error. Although it does not have any practical impact (so far it seems so), it is an eyesore to open the console and look at it, so I checked the cause of the problem on the Internet, but there is no explanation of the specific cause on the Internet, only the solution is given. This paper tries to explore the reasons in depth and jump directly to the solution if you want to see the solution directly.

Bug Details

/ / template section<span"@click="goTo('/search')"></span>
Copy the code
// Jump method
  methods: {goTo(path){
      this.$router.replace(path)
    }
  }
Copy the code

When the span is clicked repeatedly, the console will raise an error:

The error analysis

We can see the information of this error from the error information: Avoided redundant navigation to current location: (avoid redundant navigation to the current location), and we know that this error is related to promise.

Start exploring online solutions

The main solutions offered on the Internet are as follows:

  1. Reduce vue Router version to 3.0
  2. Add the following code to router index.js
// Take push as an example, replace as well
const routerPush = Router.prototype.push
Router.prototype.push = function push(location) {
  return routerPush.call(this, location).catch(error= > error)
Copy the code

3. Complete parameters of push method:

this.$router.push(route, () = > {}, (e) = > {
    console.log('Output error',e) 
})
Copy the code

The online information, however, goes only so far and does not explain the cause of the problem. Two things can be learned from these two workarounds: First, Vue Router3.0 is free of this bug; Second, this bug is solved by adding a catch to the push method. Combined with the information from the previous section, we are more certain that the bug is caused by the promise not adding a catch. Let’s take a look at the vue Router source code to see how push is implemented.

Vue Router source code exploration

Originally wanted to directly read the source code, the result encountered a small setback: source code is WRITTEN by TS… Then I thought I could convert TS into JS, but I still didn’t find what I wanted. A clever way to do this is to add console.log(vuerouter.prototype.push) to the router’s index.js file. The browser renders what we want on the console! Let’s see how push works:

VueRouter.prototype.push = function push (location, onComplete, onAbort) {
    var this$1 = this;

  // $flow-disable-line
  if(! onComplete && ! onAbort &&typeof Promise! = ='undefined') {
    return new Promise(function (resolve, reject) {
      this$1.history.push(location, resolve, reject); })}else {
    this.history.push(location, onComplete, onAbort); }};Copy the code

Internally, it calls the push method of the Router instance’s history and returns a Promise instance. New VueRouter().history.__proto __.push = new VueRouter().history.__proto __.push

  As you can see from the parameters, the push method takes three arguments: the first jump path, the second successful callback, and the third failed callback.
  HashHistory.prototype.push = function push (location, onComplete, onAbort) {
    var this$1 = this;
    var ref = this;
    var fromRoute = ref.current;
    this.transitionTo(
      location,
      function (route) {
        pushHash(route.fullPath);
        handleScroll(this$1.router, route, fromRoute, false);
        onComplete && onComplete(route);
      },
      onAbort
    );
  };
Copy the code

Internally, it calls the transitionTo method, so let’s look at its source code:

History.prototype.transitionTo = function transitionTo (location,onComplete,onAbort) {
  var this$1 = this;
  var route;
  // catch redirect option https://github.com/vuejs/vue-router/issues/3201
  try {
    route = this.router.match(location, this.current);
  } catch (e) {
    this.errorCbs.forEach(function (cb) {
      cb(e);
    });
    // Exception should still be thrown
    throw e
  }
  var prev = this.current;
  this.confirmTransition(
    route,
    function () {
      this$1.updateRoute(route);
      onComplete && onComplete(route);
      this$1.ensureURL();
      this$1.router.afterHooks.forEach(function (hook) {
        hook && hook(route, prev);
      });

      // fire ready cbs once
      if(! this$1.ready) {
        this$1.ready = true;
        this$1.readyCbs.forEach(function (cb) { cb(route); }); }},function (err) {
      if (onAbort) {
        onAbort(err);
      }
      if(err && ! this$1.ready) {
        // Initial redirection should not mark the history as ready yet
        // because it's triggered by the redirection instead
        // https://github.com/vuejs/vue-router/issues/3225
        // https://github.com/vuejs/vue-router/issues/3331
        if(! isNavigationFailure(err, NavigationFailureType.redirected) || prev ! == START) { this$1.ready = true;
          this$1.readyErrorCbs.forEach(function (cb) { cb(err); }); }}}); };Copy the code

After reading this method, we see that it calls the confirmTransition method inside. But here we can see that the parameters of these functions are similar: the first parameter is the route to be taken; The second is the successful callback function; The third is the faulty callback function. But recall that when we used the push and replace methods, we passed only one parameter, the route to which we were going. Since no error callback is given, there will naturally be no function to handle it when an error occurs. At this point we can stop delving into vuerouter’s source code.

Cause explanation and solution

Let’s go back and sort out some useful information for dealing with repeated clicks: first, the push method uses promises internally, and second, the push method actually has three parameters when we normally pass only one. So we came up with a few solutions:

  • Complete when passing parameters to the push methodonAbortParameters. This allows the push interior to handle the exception for us using the third callback passed in. (Method 3 mentioned above)
  • Since the push method returns an instance of a Promise, we can take advantage of the promise’s exception-penetrating nature and add one to it.catch, can also handle exceptions. (Method 2 mentioned above)

As for the first method (reduced version), it is not recommended, because the error report in the new version is actually for performance optimization (avoiding redundant navigation to the current location), and we should optimize it on the new version rather than going backwards.

Postscript: This article uses the push method as an example, the replace method is the same, so it will not be repeated. Due to my limited level and time, if there is any mistake or improper place, please point out the comment area, thank you!