路由切换完美取消HTTP请求

2019-05-30 · xiejiahe

通常在SPA应用中大多数情况路由跳转不会进行刷新整个页面,这样就会导致当前页面请求未处理完成而抛出错误异常。

并且这个需求是离不开SPA应用的,在写开源项目时正好有这个需求,分享下我的实现思路。

以react为示例,其他框架异同。

先看看如何取消单个请求

取消单个请求非常简单,只需要配置cancelToken参数。

const CancelToken = axios.CancelToken;
let cancel;

axios.post('/example', {
  cancelToken: new CancelToken(c => {
    cancel = c;
  })
});

// 取消请求
cancel();

取消所有请求

说下我的思路:将所有取消请求token存储在一个数组集合中,当路由发生变化时将集合所有token遍历取消再将集合置空。

实现方法如下:

0x01:

index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <script>
    // 为了方便声明在全局环境中
    var axiosCancelTokenStore = [];
  </script>
</head>
<body>
  <div id="root"></div>
</body>
</html>

0x02:

在拦截器中配置cancelToken参数

const CancelToken = axios.CancelToken;

// 请求处理
httpInstance.interceptors.request.use(function (config) {
  // do something...
  config.cancelToken = new CancelToken(cancel => {
    // 将cancel追加到集合中存储
    axiosCancelTokenStore.push(cancel);
  });
  return config;
}, function (error) {
  return Promise.reject(error);
});

// 响应处理
httpInstance.interceptors.response.use(function (res) {
  // do something...
  return res;
}, function (error) {
  // 判断请求是否已取消
  if (axios.isCancel(error)) {
    // 返回一个空Promise,将Error Handler终止传递,这一步很重要
    return new Promise(() => {});
  }
  return Promise.reject(error);
});

0x03:

由于react不像angular / vue有路由守卫,通常情况下需要自己单独封装一个路由组件。

大致如下,当路由渲染前取消所有请求

import React from 'react';
import { Switch, Route, Redirect } from 'react-router-dom';

const RouteComponent = function ({
  component: Component,
  ...rest
}) {
  // 取消所有http请求
  if (Array.isArray(axiosCancelTokenStore)) {
    // 从集合中遍历取消
    axiosCancelTokenStore.forEach(cancel => {
      if (typeof cancel === 'function') {
        cancel();
      }
    });
    // 将集合置空
    axiosCancelTokenStore = [];
  }
  return (
    <Route render={props => {
      return (
        <Component {...props} {...rest}></Component>
      )
    }} />
  )
};


当前页面请求未完成时进入下一个路由会出现如下:

说明请求已被取消,大功告成!

但是还有一种情况,有些请求用于初始化操作是不能被取消的, 这个时候可以转换下思路,在请求头添加个标识符即可, 代码如下:

请求接口

axios.post('/example', null, {
  headers: {
    cancelRequest: false
  }
});

将之前的拦截器改造下

const CancelToken = axios.CancelToken;

// 请求处理
httpInstance.interceptors.request.use(function (config) {
  // do something...
  // 判断请求头参数 cancelRequest 是否不为false
  if (config.headers.cancelRequest !== false) {
    config.cancelToken = new CancelToken(cancel => {
      axiosCancelTokenStore.push(cancel);
    });
  }
  return config;
}, function (error) {
  return Promise.reject(error);
});

// 响应处理
httpInstance.interceptors.response.use(function (res) {
  // do something...
  return res;
}, function (error) {
  // 判断请求是否已取消
  if (axios.isCancel(error)) {
    // 返回一个空Promise,将Error Handler终止传递。
    return new Promise(() => {});
  }
  return Promise.reject(error);
});

完整代码

https://github.com/xjh22222228/tomato-work/blob/master/src/utils/http.ts

https://github.com/xjh22222228/tomato-work/blob/master/src/components/private-route/index.tsx#L21

JavaScriptReact
原创文章,转载请注明出处。