和浏览器异步请求取消相关的那些事

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

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

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

1.ajax的取消

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

执行完abort以后,浏览器和被请求的服务器都会发生什么呢?MDN的解释很是的简单,就是中断已发送的请求。这个请求指的是http请求,而不是tcp链接,这样就会出现一个问题,基于http请求原理,当一个请求从客户端发出去以后,服务器端收到请求后,一个请求过程就结束了,这时就算是客户端abort这个请求,服务器端仍会作出完整的响应,只是这个响应客户端不会接收罢了。ajax

因此这个abort是仅给客户端使用的,不能做为供服务器端判断请求是否继续执行的依据chrome

那么被abort的请求对客户端有哪些影响呢?咱们能够作一个实验。api

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。promise

那么咱们能用这两个参数做为判断请求被abort的依据吗?首先可以让status等于0的状况太多了,如请求本地资源、网络不可用、请求超时,这些均可以让status被置0;readyState等于0可否做为请求是否被abort了还很差说,须要进一步判断,readyState等于0至关于请求未初始化,请求都已经send了readyState却等于0,笔者认为是能够做为abort的判断依据的,可是没法彻底证实。浏览器

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

2.页面跳转时候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”咱们的异步请求。

3.jquery对abort的处理

jquery又是如何对abort封装的呢?咱们在使用$.ajax(包括众多用$.ajax封装的方法,如$.get、$.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”。

4.jquery与页面跳转的ajax“abort”

若是仅仅是页面跳转的时候,chrome浏览器会自动执行未完成的请求的abort方法,那笔者也不会专门写一个章节去分析这个过程。由于笔者发现jquery的$.ajax这个方法中,未完成的ajax在页面跳转的时候,也会触发error事件,并且你区分不出来是浏览器取消仍是请求真的发生了error

你们能够运行以下代码,在ie和chrome两大浏览器下都会弹出的alert对话框。

var xhr = $.ajax({
    type:"get",
    url:"http://aaa.bbbbbbbb.com",
    error:function(){
        console.log(arguments);
        console.log(xhr.readyState);
        console.log(xhr.status);
        alert("执行onerror");
    }
});

setTimeout(function(){
    //模拟跳转页面
    location.href = "http://www.baidu.com"
},100);

页面一跳转就进error,并且status和readyState都是0,stutusText仅仅显示一个“error”,jquery真是让人佩(蛋)服(疼)的五体投地-_-||。

为何会这样呢?那么jquery又是如何处理onabort的呢?

笔者发现jquery1.x和2.x都会触发这个现象,因此分别参考jquery1.x和2.x的源码讨论。

jquery1.x其实并无监听onabort事件,而是统一监听onreadystatechange(具体能够参考github的xhr.jsajax.js源码),根据status是不是成功响应的http状态码,来肯定执行error仍是success方法。同时,jquery也没有获取浏览器的readyState的值,而是经过status是否为0去计算本身的xhr.readyState,能够说全部的响应全是靠status一个变量决定的,这就致使了咱们没法区分浏览器取消事件仍是真正的错误的问题。

在jquery2.x中,再也不仅监听onreadystatechange(具体能够参考github的xhr.jsajax.js源码),而是对onload、onerror、onabort(不支持onabort事件的ie9仍是监听的onreadystatechange事件)全面监听,并由这些响应事件的结果去肯定究竟执行error仍是success。这个处理看似更合理了,然而却并无什么卵用,由于没有监听chrome浏览器的readyState实际值,仍然是经过status去计算readyState,因此仍然会触发error事件,并且xhr.readyState的值仍是0,stutusText仍是仅仅显示一个“error”。

说白了这就是jquery的一个bug,仅仅是根据status是否为0去判断ajax结果,同时不返回浏览器真正的readyState值;固然咱们也能够说是浏览器的bug,为何chrome浏览器在页面跳转的时候要abort的请求呢。

无论怎么样,笔者建议在使用jquery的$ajax作异步请求的时候,千万不要在error回调中使用系统的模态框(如alert、confirm等),不然用户在使用你的页面的时候常常会出现意想不到的弹框。

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

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

虽然不能取消,可是仍是有替代品,固然这只是自欺欺人的作法,由于fetch根本没有被真正取消,他的资源也没有被释放。

标题和fetch挂钩,这让笔者感受有点大,由于笔者如今的项目中还不许备使用fetch。实际上笔者更想聊一聊我对abort和promise的见解,由于无论咱们用不用fetch,将异步请求封装成promise供后续处理都是咱们如今开发的主流作法,那么如何用promise作abrot呢?

promise仅有两个完成态,resolved和rejected。一个能够当作success处理,另外一个能够当作error处理。那咱们的abort的结果应该算在哪一个里面呢?abort确定不能将其当作success,可是abort是咱们主动的动做(也多是浏览器发出的被动abort),并非发生真的发生了错误,将其列入error看起来也不合适。

其实全部非预想的结果都是异常,因此abort固然也是异常,既然是异常就应该当rejected对待。只是resolved的处理方案由于结果是预期中的,全部处理起来比较容易,可是rejected的处理每每很困难,由于各类异常的处理方法是应该不同的。好比abort这种异常,若是是由于用户主动操做而产生的异常,那这种异常是不该该提示给用户的,因此abort引发的异常应该包装为特定的异常再进行rejected处理,以便在catch中,能够知道是什么异常,并做出对应的处理。

最后一点就是promise里resolved和rejected是不能切换的,因此一旦一个请求获得了响应,就不能再被abort了,而XMLHttpRequest对象是能够随时执行abort的,这一点也是使用promise封装异步请求和直接使用XMLHttpRequest的一个不一样点。

参考:https://github.com/camsong/blog/issues/2

相关文章
相关标签/搜索