一、Ajax
原生里可以通过XMLHttpRequest对象上的abort方法来中断ajax。注意abort方法不能阻止向服务器发送请求,只能停止当前ajax请求。
停止javascript的ajax请求有两种方式
1. 设置超时时间让ajax自动断开
2. 手动去停止ajax请求,
核心是调用XMLHttpRequest对象上的abort方法
我们以jquery举例说明:
jquery的ajax对象的abort方法:调用abort后jquery会执行error的方法,抛出abort的异常信息,此时即可执行我们中断ajax后的操作。
var ajax = $.ajax({
'error':function(jqXHR, textStatus, errorThrown){
if(errorThrown != 'abort'){
//ajax被调用abort后执行的方法
alert('您的ajax方法被停止了');
        }
    }
})
 
ajax.abort();//停止ajax原生JS
xmlHttp.open("POST","Url",true);
xmlHttp.onreadystatechange=function(){
 ...//得到响应之后的操作
}
xmlHttp.send();
//设置3秒钟后检查xmlHttp对象所发送的数据是否得到响应.
setTimeout("CheckRequest()","3000");
function CheckRequest(){
   //为4时代表请求完成了
   if(xmlHttp.readyState!=4){
     alert('数据响应超时');
     //关闭请求
     xmlHttp.close();
   }
}完整板:
<div class="page" id="app">
    <button  class="get-msg">获取数据</button>
    <button  class="cancel">撤销获取</button>
  </div>
<script>
  var currentAjax = null
    $('.get-msg').click(function () {
      currentAjax = $.ajax({
        type: 'GET',
        url: 'http://jsonplaceholder.typicode.com/comments',
        success: function (res) {
          console.log(res)
        },
        error: function (err) {
          console.log("获取失败")
        }
      })
    })
    $('.cancel').click(function () {
      if (currentAjax) {
        currentAjax.abort()
      }
    })
</script>切记:不可用abort方法来作为终止对服务器的请求操作,只有当做在前端页面立刻停止执行ajax成功后的方法,由于你执行abort方法后,ajax很可能已经对服务端发送了请求,只是还未返回回馈信息而已。
二、Axios:
关键词:CancelToken、source、source.token
应用场景:tab页频繁切换,页面的切换,用于终止正在挂起的请求
axios 的 cancelToken
axios是一个主流的http请求库,它提供了两种撤销请求的方式。
项目的如何应用
- 配置参数
axios({
	method: 'post',
	url: url,
	withCredentials: false,
	params: params,
	data: data,
	cancelToken:data.cancelToken,
	headers: headerParam
}).then(resp => {
	resolve(resp)
}).catch(error => {
	reject(error)
})在调用请求那传入令牌
cancelToken:data.cancelTokendata为传入的参数
调用接口前,传入上一步需要的令牌
let CancelToken = this.axios.CancelToken;this.source = CancelToken.source();data.cancelToken = this.source.token;axios 在 main.js中挂载到了 实例中, data data 为请求的参数,this.source中有token令牌和撤销请求的cancel方法
终止请求
cancelRequest(){
   this.source.cancel("异常信息,选填")
},在发起新的请求的时候,执行一下this.source.cancel()即可终止正在挂起的请求。
完整版:
<body>
  <div class="page" id="app">
    <button @click="getMsg" class="get-msg">获取数据</button>
    <button @click="cancelGetMsg" class="cancel">撤销获取</button>
    <ul>
      <li v-for="item in items">{{item.name}}</li>
    </ul>
  </div>
  <script>
  var app = new Vue({
    el: '#app',
    data: {
      message: 'Hello Vue!',
      items: [],
      cancel: null
    },
    methods: {
      getMsg () {
        let CancelToken = axios.CancelToken
        let self = this
        axios.get('http://jsonplaceholder.typicode.com/comments', {
          cancelToken: new CancelToken(function executor(c) {
            self.cancel = c
            console.log(c)
            // 这个参数 c 就是CancelToken构造函数里面自带的撤销请求的函数,这里把该函数当参数用
          })
        }).then(res => {
          this.items = res.data
        }).catch(err => {
          console.log(err)
        })
        //手速够快就不用写这个定时器了,点击撤销获取就可以看到效果了
        setTimeout(function () {
          //只要我们去调用了这个cancel()方法,没有完成请求的接口便会停止请求
          self.cancel()
        }, 100)
      },
      //cancelGetMsg 方法跟上面的setTimeout函数是一样的效果,由于手速不够快,哦不,是由于网速太快,导致我来不及点撤销获取按钮,数据就获取成功了
      cancelGetMsg () {
        // 在这里去判断你的id 1 2 3,你默认是展示的tab1,点击的时候不管你上一个请求有没有执行完都去调用这个cancel(),
        this.cancel()
      }
    }
  })
  </script>
</body>axios 的 cancelToken
axios是一个主流的http请求库,它提供了两种撤销请求的方式。
- 通过axios.CancelToken.source生成撤销令牌token和撤销方法cancel
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get('/user/12345', {
 cancelToken: source.token
}).catch(function(thrown) {
 if (axios.isCancel(thrown)) {
 console.log('Request canceled', thrown.message);
  } else {
 // handle error
  }
});
axios.post('/user/12345', {
 name: 'new name'
}, {
 cancelToken: source.token
})
// cancel the request (the message parameter is optional)
source.cancel('Operation canceled by the user.');
通过axios.CancelToken构造函数生成撤销函数
const CancelToken = axios.CancelToken;
let cancel;
axios.get('/user/12345', {
 cancelToken: new CancelToken(function executor(c) {
 // An executor function receives a cancel function as a parameter
 cancel = c;
  })
});
// cancel the request
cancel();需要注意的是在catch中捕获异常时,应该使用axios.isCancel()判断当前请求是否是主动撤销的,以此来区分普通的异常逻辑。
封装撤销请求逻辑
上面有两种撤销请求,用哪种都是可以的,这里使用第二种。
撤销请求主要有两个场景:
- 当请求方式method,请求路径url,请求参数(get为params,post为data)都一样时,可以视为同一个请求发送了多次,需要撤销之前的请求
- 当路由切换时,需要撤销上个路由中未完成的请求
我们封装几个方法:
// 声明一个 Map 用于存储每个请求的标识 和 撤销函数
const pending = new Map()
/**
 * 添加请求
 * @param {Object} config 
 */
const addPending = (config) => {
  const url = [
    config.method,
    config.url,
    qs.stringify(config.params),
    qs.stringify(config.data)
  ].join('&')
  config.cancelToken = config.cancelToken || new axios.CancelToken(cancel => {
    if (!pending.has(url)) { // 如果 pending 中不存在当前请求,则添加进去
      pending.set(url, cancel)
    }
  })
}
/**
 * 移除请求
 * @param {Object} config 
 */
const removePending = (config) => {
  const url = [
    config.method,
    config.url,
    qs.stringify(config.params),
    qs.stringify(config.data)
  ].join('&')
  if (pending.has(url)) { // 如果在 pending 中存在当前请求标识,需要撤销当前请求,并且移除
    const cancel = pending.get(url)
    cancel(url)
    pending.delete(url)
  }
}
/**
 * 清空 pending 中的请求(在路由跳转时调用)
 */
export const clearPending = () => {
  for (const [url, cancel] of pending) {
    cancel(url)
  }
  pending.clear()
}Map是ES6中一种新型的数据结构,本身提供了诸多方法,方便操作,适合当前场景。如果不熟悉的可以查看ECMAScript 6 入门。
在给config.cancelToken赋值的时候,需要判断当前请求是否已经在业务代码中使用了cancelToken
qs是一个专门用来转换对象和字符串参数的库,最初是由 TJ 创建并维护的,也是axios推荐使用的参数序列化库。这里我们的目的只是单纯的将参数对象转换为字符串方便拼接。
Map结构默认部署了Symbol.iterator属性,可以使用for…of循环直接获取键名和键值,当然你也可以使用for…in循环。
在 axios 拦截器中使用
主要的方法已经写好了,只需要添加到axios拦截器中就可以了。
axios.interceptors.request.use(config => {
  removePending(options) // 在请求开始前,对之前的请求做检查撤销操作
  addPending(options) // 将当前请求添加到 pending 中
  // other code before request
  return config
}, error => {
  return Promise.reject(error)
})
axios.interceptors.response.use(response => {
  removePending(response) // 在请求结束后,移除本次请求
  return response
}, error => {
  if (axios.isCancel(error)) {
    console.log('repeated request: ' + error.message)
  } else {
    // handle error code
  }
  return Promise.reject(error)
})将clearPending()方法添加到vue路由钩子函数中
router.beforeEach((to, from, next) => {
  clearPending()
  // ...
  next()
})三、fetch
以下是撤销 Fetch 请求的基本步骤:
const controller = new AbortController();
const { signal } = controller;
fetch("http://localhost:8000", { signal }).then(response => {
    console.log(`Request 1 is complete!`);
}).catch(e => {
    console.warn(`Fetch 1 error: ${e.message}`);
});
// Abort request
controller.abort();在 abort 调用时发生 AbortError,因此你可以通过比较错误名称来侦听 catch 中的中止操作。
}).catch(e => {
    if(e.name === "AbortError") {
        // We know it's been canceled!
    }
});将一样的信号传递给多个 fetch 调用将会撤销该信号的所有请求:
const controller = new AbortController();
const { signal } = controller;
fetch("http://localhost:8000", { signal }).then(response => {
    console.log(`Request 1 is complete!`);
}).catch(e => {
    console.warn(`Fetch 1 error: ${e.message}`);
});
fetch("http://localhost:8000", { signal }).then(response => {
    console.log(`Request 2 is complete!`);
}).catch(e => {
    console.warn(`Fetch 2 error: ${e.message}`);
});
// Wait 2 seconds to abort both requests
setTimeout(() => controller.abort(), 2000);样板:
function abortableFetch(request, opts) {
  const controller = new AbortController();
  const signal = controller.signal;
  return {
    abort: () => controller.abort(),
    ready: fetch(request, { ...opts, signal })
  };
}说实话,我对撤销 Fetch 的方法并不感到兴奋。在理想的世界中,通过 Fetch 返回的 Promise 中的 .cancel() 会很酷,但是也会带来一些问题。无论如何,我为能够撤销 Fetch 调用而感到高兴,你也应该如此!
使用AbortController来撤销 fetch
AbortController 是 JavaScript 的最新版本中的特性,它是在 fetch 被实现之后出现的。 更好的消息是所有现代浏览器都支持它。
AbortController 包含一个 abort 方法。 它还包含一个可以传递给fetch的signal属性。 当调用 AbortController.abort 时,fetch请求就会被撤销。
让我们在 getCharacter 的fetch请求中使用 AbortController 及其signal属性:
function getCharacter(id: number) {
  // 获取AbortController实例
  const controller = new AbortController();
  // 获取 signal属性
  const signal = controller.signal;
  const promise = new Promise(async (resolve) => {
    const response = await fetch(`https://swapi.dev/api/people/${id}/`, {
      method: "get",
      // 将 signal作为fetch的参数之一
      signal,
    });
    const data = await response.json();
    assertIsCharacter(data);
    resolve(data);
  });
  // 设置一个 撤销函数
  promise.cancel = () => controller.abort();
  return promise;
}我们从getCharacter函数中删除了async关键字,并将现有代码包装在一个新的Promise中。 当请求到数据后,我们使用resolve将数据抛出去。 我们在Promise中添加了一个cancel方法,该方法调用了AbortController.abort。
包含 cancel 方法的 Promise 从 getCharacter 中被返回,以便我们在业务代码中可以使用它来撤销请求。
保存代码查看界面效果,发现有一个类型错误:
// - Property 'cancel' does not exist on type 'Promise<unknown>'
promise.cancel = () => controller.abort();让我们为Promise的 cancel 方法创建一个类型
interface PromiseWithCancel<T> extends Promise<T> {
  cancel: () => void;
}
// 使用 然使用类型断言来解决前面的类型错误
function getCharacter(id: number) {
  ...
  (promise as PromiseWithCancel<Character>).cancel = () => controller.abort();
  return promise as PromiseWithCancel<Character>;
}在 React 组件中使用getCharacter
我们将把getCharacter返回的 promise 存储在一个名为query的状态变量中。
export function App() {
  const [status, setStatus] = React.useState<"loading" | "loaded" | "cancelled">("loading");
  const [data, setData] = React.useState<Character | undefined>(undefined);
  const [query, setQuery] = React.useState<PromiseWithCancel<Character> | undefined>(undefined);
  React.useEffect(() => {
    const q = getCharacter(1);
    setQuery(q);
    q.then((character) => {
      setData(character);
      setStatus("loaded");
    });
  }, []);
  ...目前,当点击撤销按钮的时候,我们调用 promise 中的cancle方法
<button
  onClick={() => {
    query?.cancel();
    setStatus("cancelled");
  }}
>
  Cancel
</button>点击撤销按钮后吗,我们看到发现’Cancelled‘文案被渲染出来了

编辑
添加图片注释,不超过 140 字(可选)
再通过谷歌开发者工具查看网络请求,发现请求也被撤销了

编辑切换为居中
添加图片注释,不超过 140 字(可选)
捕获”撤销请求”发生的错误
让我们再看看控制台,当请求被撤销后,有错误被抛出了

编辑切换为居中
添加图片注释,不超过 140 字(可选)
我们可以使用 try catch 来包裹请求以便捕获错误
const promise = new Promise(async (resolve) => {
  try {
    const response = await fetch(`https://swapi.dev/api/people/${id}/`, {
      method: "get",
      signal,
    });
    const data = await response.json();
    assertIsCharacter(data);
    resolve(data);
  } catch (ex: unknown) {
    if (isAbortError(ex)) {
      console.log(ex.message);
    }
  }
});isAbortError类型的函数如下
function isAbortError(error: any): error is DOMException {
  if (error && error.name === "AbortError") {
    return true;
  }
  return false;
}目前当我们再次点击撤销按钮,我们会在控制台收到一条消息提示而不是一个错误

编辑
添加图片注释,不超过 140 字(可选)
总结
可以将AbortController中的signal属性传递给fetch。 然后可以调用AbortController.abort撤销请求。
撤销 fetch 会引发一个错误,但可以使用try catch将其捕获。
 
                
 
                 
                 
                





 
                 
                 
                
收藏了,感谢分享