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)
})
}
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:
this.$router.push()
is executing?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.