Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Uncaught (in promise) undefined - Vue-router

I'm facing a really strange issue where if a user with restricted permissions tries logging into my web app, they see the following error:

Uncaught (in promise) undefined

But this doesn't happen with users who have max permissions.

I think the issue is being caused by the re-reoute. If the user does not have page_access 1, it then routes to /holidays. The other strange thing is that this error only ever appears the once, and that's when the user first logs in. If the page is refreshed or the user navigates away to other pages, it does not appear.

router.js

Vue.use(Router)

const router = new Router({

  routes: [
    {
      path: '/',
      name: 'dashboard',
      component: Dashboard,
      beforeEnter(to, from, next) {
        if(localStorage.token) {
          if(localStorage.page_access.indexOf('1') != -1 && localStorage.page_access != null) {
            next('/holidays');
          }
          else {
            next();
          }
        } else {
          next('/login');
        }
      }
    },
    {
      path: '/holidays',
      name: 'holidays',
      component: Holidays,
      beforeEnter(to, from, next) {
        if(localStorage.token) {
          next();
        } else {
          next('/login');
        }
      }
    },
  ],
  mode: 'history'
})

router.beforeResolve((to, from, next) => {

  if(localStorage.token && from.name != 'login' && to.name != 'login') {
    store.dispatch('autoLogin')
    .then(response => {
      store.dispatch('getNavigation');
      next();
    })
    .catch(err => {
      console.log(err);
    });
  }
  else if(from.name && !localStorage.token) {
    router.go('/login');
  }
  else {
    next();
  }
});

export default router;

store.js

async autoLogin({commit}) {
    const token = localStorage.getItem('token');
    const remember_token = localStorage.getItem('remember_token');

    if(!token) {
      return;
    }

    try {
      const res = await axios({
      method: 'post',
      data: { userId: localStorage.user_id, token: localStorage.remember_token },
      url: 'https://controlapi.totalprocessing.com/api/get-user',
      config: { headers: { 'Content-Type': 'application/x-www-form-urlencoded' }}
      })
      .then(response => {
        if(response.data.remember_token == remember_token) {
          commit('authUser', { token: token });
          return response;
        }
        else {
          localStorage.clear();
          return null;
        }
      })
      .catch(e => {
          this.errors.push(e);
          return e;
      })
      return res;
    }
    catch(e) {
      console.log(e);
      return e;
    }
}
getNavigation({commit}) {
    let pageAccess = localStorage.page_access == 'null' ? null : localStorage.page_access;
    let subPageAccess = localStorage.sub_page_access == 'null' ? null : localStorage.sub_page_access;

    axios({
    method: 'post',
    data: { pageAccess: pageAccess, subPageAccess: subPageAccess },
    url: 'https://controlapi.totalprocessing.com/api/client-get-navigation',
    config: { headers: { 'Content-Type': 'application/x-www-form-urlencoded' }}
    })
    .then(response => {
    console.log(response.data);
        const data = response.data;
        const tree = [];

        data.reduce(function(a, b, i, r) {

            // Add the parent nodes
            if(a.page_id != b.page_id){
                tree.push({ page_id: a.page_id,
                            page_name: a.page_name,
                            page_path: a.path,
                            page_icon: a.page_icon
                            });
            }

            // Add the last parent node
            if(i+1 == data.length) {
                tree.push({ page_id: b.page_id,
                            page_name: b.page_name,
                            page_path: b.path,
                            page_icon: b.page_icon
                            });

                // Add the child nodes to the parent nodes
                data.reduce(function(a, b) {
                    if(a.sub_page_id) {
                        const find = tree.findIndex(f => f.page_id == a.parent_id);

                        // Add the first child node to parent
                        if(!("children" in tree[find])) {
                            tree[find].children = [];

                            tree[find].children.push({ page_id: a.sub_page_id,
                                                    page_name: a.sub_page_name,
                                                    page_path: a.sub_page_path,
                                                    page_icon: a.sub_page_icon
                            });
                        }
                        // Add the remaining child nodes to parent nodes
                        else {
                            tree[find].children.push({ page_id: a.sub_page_id,
                                                    page_name: a.sub_page_name,
                                                    page_path: a.sub_page_path,
                                                    page_icon: a.sub_page_icon
                            });
                        }
                    }
                    return b;
                });
            }
            return b;
        });

        commit('authNav', {
        navigation: tree
        });
    })
    .catch(e => {
        this.errors.push(e)
    })
}
like image 796
Dally Avatar asked Aug 14 '19 11:08

Dally


People also ask

How to debug a route change?

During your debugging, add console.log () s into all your components' created/mounted and destroyed lifecycle methods, and also into the functions related to the route change. You should be able to get a grasp on the way data is flowing.

How does router-link work with vue router?

When using router-link, Vue Router calls router.push to trigger a navigation. While the expected behavior for most links is to navigate a user to a new page, there are a few situations where users will remain on the same page:

What happens if I add await to a promise?

Adding await would delay downstream code so it reads the resolved promise rather than the in-flight promise. In my case I just needed to add a catch to the router.push method:


5 Answers

Based on my experience over the past few days, it is critical to catch errors in the function that calls this.$router.push().

I find two ways are immediately quite viable:

handleSomething() {
    this.$router.push({}).catch((err) => {
        throw new Error(`Problem handling something: ${err}.`);
    });
},

and

async handleSomething() {
    try {
        await this.$router.push({});
    } catch (err) {
        throw new Error(`Problem handling something: ${err}.`);    
    }
},

At the moment, I prefer against the async/await technique here because of its execution-blocking nature, but the key observation you should make is that the "uncaught in promise" error itself is a known issue in JavaScript often referred to as "a promise being swallowed", and it's caused by a Promise being rejected but that "error" is swallowed because it isn't properly caught. That is to say there is no block of code that catches the error, so your app cannot do anything in response to the error.

This means it is paramount to not swallow the error, which means you need to catch it somewhere. In my two examples, you can see the error will pass through the catch blocks.

Secondary to the error swallowing, is the fact that the error is even thrown to begin with. In my application where I saw this, it was hard to debug, but I can see the nature of the error has something to do with Vue components unloading and loading as the route changes. For example, if you call this.$router.push() in a component and then that component gets destroyed while the route-change is in-progress, it is reasonable that you could see an error like this.

As an extension of this problem, if a route-change occurs and the resultant component tries to read data from the .push() event before the Promise is resolved, it could also throw this error. The await should stop an error like that by instructing your application to wait before reading.

So in short, investigate those two things:

  1. are you somehow destroying/creating components while this.$router.push() is executing?
  2. is the next component possibly reading data about route parameters before the route-change is completed?

If you discover some of this could be happening, consider your data flow and make sure you solve it by taming the async behaviour, not just by suppressing the error. In my opinion, the error is a symptom of something bigger.

During your debugging, add console.log()s into all your components' created/mounted and destroyed lifecycle methods, and also into the functions related to the route change. You should be able to get a grasp on the way data is flowing.

I suspect the nature of this issue stems from downstream usage of this.$route.params during an in-flight route-change. Add lots of console.logs and/or step through a debugger.

like image 168
agm1984 Avatar answered Oct 16 '22 15:10

agm1984


In my case I just needed to add a catch to the router.push method:

router.push({query: newQueryObj)}).catch(e => {})

See this related issue for more details.

like image 40
mcmimik Avatar answered Oct 16 '22 15:10

mcmimik


I also came across this issue and changing !variable to variable !== in the router did the trick.

else if(from.name && !localStorage.token) {
    router.go('/login');
}

to

else if(from.name && localStorage.token === '') {
    router.go('/login');
}
like image 3
at0dd Avatar answered Oct 16 '22 15:10

at0dd


add main.js;

import Router from 'vue-router'
const routerPush = Router.prototype.push
Router.prototype.push = function push(location) {
  return routerPush.call(this, location).catch(error=> error)
}

or; npm i [email protected] -S

like image 2
enso Avatar answered Oct 16 '22 15:10

enso


Our problem is the same. You can jump to this path on the login page and write:

this.$router.push({ path: this.redirect || '/' }, onComplete => { }, onAbort => { })

The wrong way to write is:

 this. $router. Push ({path: this. Redirect | '/'})
like image 2
hanpanpan Avatar answered Oct 16 '22 15:10

hanpanpan