【问题标题】:Issue with cancelling axios .then logic with interceptors and cancel token取消 axios .then 逻辑与拦截器和取消令牌的问题
【发布时间】: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

进度更新:

我在后端添加了一些逻辑,如果登录成功,

  1. 我将 JWT 令牌的过期时间传递回我的前端。

  2. 然后将该过期时期推送到我的 redux 存储区。

  3. 对于每个请求,在我下面的“AxiosInterceptor.js”文件中,在返回配置之前,我会验证 redux 中设置的 exp 值。

现在这在初始登录时可以正常工作,但是一旦令牌过期并且您收到来自“Swal.fire”的弹出窗口并单击“返回”,它会做两件事:

  1. 调用 logOut 操作并将所有值返回到初始状态。 (这很好用。我使用 redux-devtools-extension 进行了验证)
  2. 现在我可以重新登录。一切都开始正常加载,但随后我看到“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


【解决方案1】:

我将问题追溯到 react-redux 中的钩子“useSelector”。在它已经返回正确的数据之后,这似乎是返回缓存数据的方式。我当时使用的是 7.2 版,但我也在 v7.1 上确认了它。我没有在任何其他版本上测试过。我通过在下面的getExpire() 函数中从 redux-persist Storage(localStorage) 中提取数据来解决这个问题。不是最优雅的解决方案,但我的应用程序现在可以正常运行。

export default withRouter({
    useSetupInterceptors: (history) => {
        const { onLogout } = useLogout(history);
        const CancelToken = axios.CancelToken;
        const { onExp } = useExp();

        axios.interceptors.request.use((config) => {
            const testexp = onExp();
            if (testexp) {
                Swal.fire({
                    title: '401 - Authorization Failed',
                    text: '',
                    icon: 'warning',
                    showCancelButton: false,
                    confirmButtonText: 'Return',
                }).then((result) => {
                    onLogout();

                })
                return {
                    ...config,
                    cancelToken: new CancelToken((cancel) => cancel('Cancel repeated request'))
                };
            } else {
                return config;
            }
        }, error => { console.log(error) });

        axios.interceptors.response.use(response => {
            return response;
        }, error => {
            try {
                if (axios.isCancel(error)) {
                    return new Promise(() => { });
                }
                return Promise.reject(error);
            } catch (error) {
                console.log(error)
            }
        });
    },
});

function getExpire () {
    var localStore = localStorage.getItem("persist:root")
    if (localStore) {
       let store = JSON.parse(localStore)
       return JSON.parse(store.exp)
    } 
    return 0

}

function useExp() {
   // const currentExp = useSelector(state => state.exp)
    return {
        onExp() {
            if (Date.now() > getExpire().exp) {
                return true
            } else { return false }
        },
    }
}

function useLogout(history) {
    const dispatch = useDispatch()
    return {
        onLogout() {
            dispatch(allActions.expAction.setLogout())
            history.push("/login");
        },
    }
}

【讨论】:

    猜你喜欢
    • 2023-04-05
    • 2020-10-01
    • 2019-05-30
    • 2021-12-20
    • 2019-11-26
    • 2018-10-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多