Reputation: 1427
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)
})
}
Upvotes: 21
Views: 40025
Reputation: 1124
router.push
does return a Promise. and Promise rejection need to be handled.
instead of adding try catch
or .catch
everywhere in the project. you can build a global router error handler, or a component error handler by wrapping router.push
function.
this can be implemented in your component scope or your global scope.
*be aware that the global implementation will ignore any .catch
on the router.push
if it is used by others. so think twice before using the global solution.
global example:
const originalPush = router.push
router.push = (location)=>{
return originalPush.call(router,location).catch(e=>{
console.log(`error in router ${e.message}`);
})
}
new Vue({
router,
store,
components: {
App
},
template: "<App/>",
render: (h) => h(App),
}).$mount('#app');
if you want component scope only. then wrap this.$router.push
. this is considered as safer solution.
Upvotes: 0
Reputation: 21
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 | '/'})
Upvotes: 2
Reputation: 17132
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.
Upvotes: 20
Reputation: 1813
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.
Upvotes: 8
Reputation: 69
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
Upvotes: 3
Reputation: 31
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');
}
Upvotes: 3