【发布时间】:2020-03-26 13:39:43
【问题描述】:
我为我的 react 应用设置了一个 axios 响应拦截器。它可以很好地捕获大多数错误,但我遇到的一件事是如果响应是 401 即用户未经过身份验证,拦截器会将用户发送回登录页面。现在这可行,但来自原始请求的 .then 内部的逻辑仍然运行。这会导致类型错误,如 .then 逻辑中我正在使用响应数据设置状态。这是我目前尝试实现一个不起作用的 axios 取消令牌。请参阅下面的代码。我在这里想念什么?实现这一点的最佳方法是什么,而不必在每个 axios 请求中添加 If/Else 逻辑以检查“数据”是否存在或响应是否为 401、200 ...?
AxiosInterceptor.js ...
export default withRouter({
useSetupInterceptors: (history) => {
axios.interceptors.response.use(response => {
return response;
}, error => {
try {
if (error.response.status === 401) {
history.push("/login");
Swal.fire({
title: '401 - Authorization Failed',
text: '',
icon: 'warning',
showCancelButton: false,
confirmButtonText: 'Close',
})
throw new axios.Cancel('Operation canceled');
}
return Promise.reject(error);
} catch (error) {
console.log(error)
}
});
},
});
UserPage.js ...
function userPage() {
var [pageData, setPageData] = useState('');
var classes = useStyles();
useEffect(() => {
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
const loadData = () => {
try {
axios.post('/api/getUserData', { cancelToken: source.token })
.catch(function (error) {
source.cancel();
})
.then(res => {
const data = res.data;
setPageData(data);
})
} catch (error) {
if (axios.isCancel(error)) {
console.log('Op Cancel')
} else {
throw error;
}
}
};
loadData();
return () => {
source.cancel();
};
}, []);
return (
...
);
}
...
我得到的错误:
Unhandled Rejection (TypeError): Cannot read property 'data' of undefined
进度更新:
我在后端添加了一些逻辑,如果登录成功,
我将 JWT 令牌的过期时间传递回我的前端。
然后将该过期时期推送到我的 redux 存储区。
对于每个请求,在我下面的“AxiosInterceptor.js”文件中,在返回配置之前,我会验证 redux 中设置的 exp 值。
现在这在初始登录时可以正常工作,但是一旦令牌过期并且您收到来自“Swal.fire”的弹出窗口并单击“返回”,它会做两件事:
- 调用 logOut 操作并将所有值返回到初始状态。 (这很好用。我使用 redux-devtools-extension 进行了验证)
- 现在我可以重新登录。一切都开始正常加载,但随后我看到“Swal.fire”对话框返回登录页面。将 user.exp 和 date.now 记录到控制台时,我看到了一些奇怪的行为(参见 cmets):
// from redux-logger
action SET_EXP @ 20:05:42.721
redux-logger.js:1 prev state {user: {…}, _persist: {…}}
redux-logger.js:1 action {type: "SET_EXP", payload: 1585267561036}
USEREXP 1585267561036 // this is the new EXP time set in redux, received from back end on login
AxiosInterceptors.js:17 Current Status = 1585267561036 false // first two axios calls on main page validate and indicate not expired
AxiosInterceptors.js:17 Current Status = 1585267561036 false
AxiosInterceptors.js:17 Current Status = 1585267495132 true // this is the value of the previos exp value that was set
AxiosInterceptors.js:17 Current Status = 1585267495132 true
AxiosInterceptors.js:17 Current Status = 1585267352424 true // this is the value that was set two login times ago
AxiosInterceptors.js:17 Current Status = 1585267352424 true
这怎么可能?我用 redux-devtools 验证过,一旦我 返回登录页面,确实是空的。 > redux-store 中的值似乎正在回滚到旧值?我正在使用 chrome 版本 74.0.3729.131(官方版本)(64 位)。我尝试过使用隐身模式并清除缓存和 cookie。
新的 AxiosInterceptor.js ...
export default withRouter({
useSetupInterceptors: (history) => {
let user = useSelector(state => state.user)
axios.interceptors.request.use(config => {
const { onLogo } = useLogout(history);
console.log("Current Status = ", user.exp, Date.now() > user.exp)
if (Date.now() > user.exp) {
Swal.fire({
title: '401 - Auth Failed',
text: '',
icon: 'warning',
showCancelButton: false,
confirmButtonText: 'Return',
}).then((result) => {
onLogo();
})
return {
...config,
cancelToken: new CancelToken((cancel) => cancel('Cancel')) // Add cancel token to config to cancel request if redux-store expire value is exceeded
};
} else {
return config;
}
}, error => { console.log(error)});
axios.interceptors.response.use(response => {
return response;
}, error => {
try {
if (axios.isCancel(error)) { // check if canceled
return new Promise(() => {}); // return new promise to stop axios from proceeding to the .then
}
if (error.response.status === 401) {
history.push("/login");
Swal.fire({
title: '401 - Auth Failed',
text: '',
icon: 'warning',
showCancelButton: false,
confirmButtonText: 'Close',
})
throw new axios.Cancel('Operation canceled');
}
return Promise.reject(error);
} catch (error) {
console.log(error)
}
});
},
});
function useLogo(history) {
const dispatch = useDispatch()
return {
onLogo() {
dispatch(allActs.userActs.logOut())
history.push("/login");
},
}
}
【问题讨论】:
-
查看
then(res => { console.log(res)})的响应是什么 -
感谢您的快速响应!我知道我可以检查响应并使用 If/Else 逻辑来决定在发生 401 时该怎么做,但我想看看是否有办法在拦截器/全局级别执行此操作,而不必添加这个到每个页面上的每个 axios 请求。
-
不幸的是,我查看了那个帖子,看来我的问题已经从那里转移了。
-
你检查 axios-middleware 了吗?您还可以创建自己的中间件来全局处理错误。
标签: javascript reactjs redux axios redux-persist