vlambda博客
学习文章列表

Ajax取消相关的那些事

我们开发web页面时候,也许会遇到和异步请求取消相关的问题。

如:在一个请求发送之后,用户做了一个取消指令,为了节省资源,我们需要把已经被用户取消的请求终止掉;或者是一个页面正在用ajax请求后台,突然页面发生了跳转,而我们未完成的ajax莫名其妙地走进了error里面了。

为了解决这两问题,我们今天一起看看和异步请求取消相关的那些事。

Ajax的取消

当我们创建一个XMLHttpRequest对象的时候,我们就会发现两个api——abort和onabort,这就是终止异步请求的方法与其响应事件。

执行完abort之后,浏览器和被请求的服务器都会发生什么呢?

MDN的解释非常的简单,就是中断已发送的请求。这个请求指的是http请求,而不是tcp连接,这样就会出现一个问题,基于http请求原理,当一个请求从客户端发出去之后,服务器端收到请求后,一个请求过程就结束了,这时就算是客户端abort这个请求,服务器端仍会做出完整的响应,只是这个响应客户端不会接收罢了。

所以这个abort是仅给客户端使用的,不能作为供服务器端判断请求是否继续执行的依据。

那么被abort的请求对客户端有哪些影响呢?我们可以做一个实验。

var xhr = new XMLHttpRequest();
xhr.open("GET","#");
xhr.send();
xhr.onload = function(){
    console.log("abort前");
    console.log(xhr.readyState);
    console.log(xhr.status);
    xhr.abort();
    console.log("abort后");
    console.log(xhr.readyState);
    console.log(xhr.status);
}

我们可以看到readyState和status在abort之后被重置回0。

那么我们能用这两个参数作为判断请求被abort的依据吗?

首先能够让status等于0的情况太多了,如请求本地资源、网络不可用、请求超时,这些都可以让status被置0;readyState等于0能否作为请求是否被abort了还不好说,需要进一步判断,readyState等于0相当于请求未初始化,请求都已经send了readyState却等于0,笔者认为是可以作为abort的判断依据的,但是无法完全证明。

有没有更可靠的证明请求是否执行了abort方法呢?有,答案是使用onabort,onabort作为abort的响应函数,是最直接有效的判断abort手段。

页面跳转时候ajax会自动“abort”

笔者从前认为abort离我很远,但是在实际项目中,笔者发现页面我开发的请求经常被abort。这个abort动作当然不是我发起的,也不是用户发起的,他是浏览器自动发起的。笔者发现一个页面跳转的时候,浏览器会自动把所有响应未完成的请求执行“abort”,而响应已完成的请求则不会这样。我们可以做一个实验

//要在chrome或者webkit内核上运行
var xhr = new XMLHttpRequest();
//访问一个不存在的地址 取保请求不会马上响应
xhr.open("GET","http://aaa.bbbbbbbb.com");
xhr.send();
xhr.onabort = function(){
    console.log(xhr.readyState);
    console.log(xhr.status);
    alert("执行onabort");
};
setTimeout(function(){
    //模拟跳转页面
    location.href = "http://www.baidu.com"
},0);

结果网页上弹出了一个alert,显示着"执行onabort"。

再看控制台,我们会发现status不变还是0,而readyState却是4,这也是浏览器发出的abort和手动执行abort最大不同。

以上测试仅在chrome上有效,ie、edge、火狐在页面跳转的时候,不会触发未完成的请求的onabort事件,但是会触发onreadystatechange事件。不管怎么讲,当页面发生跳转的时候,浏览器可能会“abort”我们的异步请求。

jQuery对abort的处理

jquery又是如何对abort封装的呢?我们在使用 .ajax封装的方法,如 .post)的时候,会返回一个xhr对象,这个基于$.deferred.promise封装的jquery自己的对象,而不是原始的XMLHttpRequest或者ie的ActiveXObject对象。在这个对象中定义了如abort等方法,使得开发者可以手动abort一个ajax请求。

var xhr = $.ajax(url);
xhr.abort();

另外,jquery的超时也是通过setTimeout和abort实现的,所以当你使用jquery发出的请求超时的时候,实际上是被jquery把请求abort了。如何区分jquery的超时和手动abort呢?方法就是靠stutusText,对于timeout和abort两个客户端做出的响应,jquery会给stutusText设定固定的值,abort的时候,stutusText的值为“abort”,超时的时候stutusText值是“timeout”。

fetch及promise如何取消与取消处理的

fetch作为ajax的升级版,越来越多的浏览器已经支持他了,那fetch又是如何取消异步请求的呢?答案是fetch暂时不能被取消...,因为没有对应的api。

Fetch 也有它的不足,相对于 XHR  来说,目前它具有以下劣势:

  • 不能取消(虽然 AbortController 能实现,但是目前兼容性基本不能使用,可以使用 polyfill )
  • 不能获取进度
  • 不能设置超时(可以通过简单的封装来模拟实现)
  • 兼容性目前比较差(可以使用 polyfill 间接使用 XHR 来优雅降级,这里推荐使用 isomorphic-fetch )
var oldFetchfn = fetch; 
var controller = new AbortController();
var signal = controller.signal;

var downloadBtn = document.querySelector('.download');
var abortBtn = document.querySelector('.abort');

downloadBtn.addEventListener('click', fetchVideo);

abortBtn.addEventListener('click'function({
  controller.abort();
  console.log('Download aborted');
});

function fetchVideo({
  ...
  fetch(url, {signal}).then(function(response{
    ...
  }).catch(function(e{
    reports.textContent = 'Download error: ' + e.message;
  })
}