相信大家在工作中经常需要使用AJAX,所以当大家看到文章标题的时候可能会觉得这是一个老生常谈的话题。
前端开发中向后端发起xhr(XMLHttpRequest)请求(代表性的就是熟悉的ajax)是再正常不过的事。
但在前端开发过程中,不怎么重视xhr的abort(中止掉xhr请求,及表示取消本次请求)。往往会带来一些不可意料的结果。
比如:切换tab,发起xhr请求,渲染同一个列表。就这么简单的拉取数据渲染列表的功能,并且可以根据tab切换。想想应该是很简单。但是假如你只顾着发起xhr请求,而没有abort掉它,想想会发生什么。很有可能就是当前选中的tab数据,并不是你想要的。说白了就是数据错了。这时候你可能就要考虑是不是xhr请求返回数据的顺序问题。
答案是肯定的,xhr请求返回数据顺序是不固定的。所以你要做的就是abort掉你之前的xhr请求,然后再发起一个新的xhr请求。
结合上面所说的例子可以知道xhr使用不当会存在以下问题:
容易出现页面最终数据与状态不一致的问题,这可能再列表筛选是出现的概率比较大。
xhr请求达到一定数量之后,浏览器就会显得非常的慢。因为有太多的请求在请求服务器资源。
为了解决上面的问题,我们在进行页面的时候就必须考虑abort掉所有的xhr请求。
那么如何实现xhr的abort方法呢,或者通过何种方式abort掉xhr呢?
一个简单的xhr
我们都知道,现在的框架(例如:jQuery的ajax模块)对xhr都进行了封装,是为了让我们更好的使用xhr。但是也蒙蔽了我们的眼睛。让我们抛开框架,来看看一个简单的xhr怎么实现。
//仅供参考 xhr function ajax(type ,url , data , successCallBack , errorCallBack){ let xhr = new XMLHttpRequest(); xhr.onload = ()=>{ if(xhr.status === 200){ return successCallBack(xhr.response||xhr.responseText); } return errorCallBack('请求失败'); } xhr.onerror = ()=>{ return errorCallBack('出错了'); } xhr.open(type,url); xhr.send(data ? data:null); }
这就是一个简单的xhr请求的实现,我把它命名为ajax,我们现在可以通过以下方式进行调用:
ajax('get','/test/getUserList' , undefined , function(result){ console.log('成功了。', result); } ,function(error){ console.log(error); });
如果使用这个方法我们是没办法abort掉xhr请求的。好吧,现在我们把它改造一下,让它支持abort方法:
//仅供参考 xhr.abort function ajax(type ,url , data , successCallBack , errorCallBack){ let xhr = new XMLHttpRequest(); xhr.onload = ()=>{ if(xhr.status === 200){ return successCallBack(xhr.response||xhr.responseText); } return errorCallBack('请求失败'); } xhr.onerror = ()=>{ return errorCallBack('出错了'); } xhr.open(type,url); xhr.send(data ? data:null); return xhr;//返回XMLHttpRequest实例对象 }
好像没有什么变化对吧。不错,只要在函数的末尾添加return xhr;将XMLHttpRequest实例对象返回即可。那我们在就已经可以如愿的abort掉xhr请求。
let xhr = ajax('get','/test/getUserList' , undefined , function(result){ console.log('成功了。', result); } ,function(error){ console.log(error); }); //abort xhr.abort();
好像我们已经大功告成了。但是问题来了,现在Promise这么好用,为什么不把它加进来呢。像这样没法在我们的Promise链式调用上使用它。
Promise封装xhr
好了,现在的首要任务是封装出一个Promise版的ajax库。首要要确认的就是,ajax方法需要返回的是Promise实例对象,而不再是原生的XMLHttpRequest实例对象。知道了这一点那就可以进行封装了。
//仅供参考 promise function ajax(type ,url , data ){ let xhr = new XMLHttpRequest(); let promise = new Promise(function(resolve , reject){ xhr.onload = ()=>{ if(xhr.status === 200){ return resolve(xhr.response||xhr.responseText); } return reject('请求失败'); } xhr.onerror = ()=>{ return reject('出错了'); } xhr.open(type,url); xhr.send(data ? data:null); }); return promise;//返回Promise实例对象 }
使用了Promise之后我们不再需要传入回调函数。所以参数减少了。这样我们就可以愉快的进行链式调用了。
let promise = ajax('get','/test/getUserList'); promise.then((result)=>{ console.log('成功了。', result); },(error)=>{ console.log(error); })
可问题又来了,Promise实例是没有abort方法的。假如我们把ajax方法修改为返回xhr,我们是可以如期调用abort方法杀死请求,但是我们就不能使用Promise带给我们的好处了。
仔细思考,最后一句return promise; 这里是不能改。我们只能另外想办法。
最简单的解决方式就是创建一个xhr和promise的映射关系。也就是每一个promise对应一个唯一的xhr请求。有了思路之后,解决方案就来了。