深⼊讲解xhr(XMLHttpRequest)jsonp请求之abort
前⾔
相信⼤家在⼯作中经常需要使⽤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();
if(xhr.status === 200){
return sponse||sponseText);
}
return errorCallBack('请求失败');
}
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();
if(xhr.status === 200){
return sponse||sponseText);
}
return errorCallBack('请求失败');
}
return errorCallBack('出错了');
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){
if(xhr.status === 200){
return sponse||sponseText);
}
return reject('请求失败');
}
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请求。有了思路之后,解决⽅案就来了。
let map = [];//⽤于保存promise和xhr之间的映射关系
//仅供参考 promise abort
function ajax(type ,url , data ){
let xhr = new XMLHttpRequest();
let promise = new Promise(function(resolve , reject){
if(xhr.status === 200){
return sponse||sponseText);
}
return reject('请求失败');
}
return reject('出错了');
});
map.push({promise:promise,request:xhr});//创建promise和xhr之间的映射关系,保存到全局的⼀个数组中。
return promise;//返回Promise实例对象
}
//abort 请求
function abort(promise){
for(let i = 0 ; i < map.length ; i++ ){
if ( map[i].promise === promise ){
map[i].request.abort();
}
}
}
通过在全局创建⼀个map保存所有的promise和xhr之间的映射关系。这样我们就可以在需要abort请求的时候根据映射关系到xhr并abort请求。
let promise = ajax('get','/test/getUserList');
promise.then((result)=>{
console.log('成功了。', result);
},(error)=>{
console.log(error);
})
abort(promise);
好吧,到这⾥Promise版的ajax,我们已经实现了。是不是很简单啊。
何为jsonp
假如你还不明⽩jsonp是何物,那希望下⾯的篇幅能让你明⽩。可能你零星的知道跨越请求,但是可能没有在实战中碰到过。那么我们先来看看,⼀个简单的jsonp函数是怎么实现的吧。
let index = 0;
//仅供参考 jsonp
function jsonp(url,jsonp,successCallback , errorCallback){
let script = ateElement('script');
let result ;
successCallback(result);
}
errorCallback('出错了');
}
let callBackName = 'jsonpCallback'+index++;
script.src=url+(url.indexOf('?') >=0 ? '&':'?')+jsonp+'='+callBackName;
window[callBackName]=function(){//拿给后端进⾏输出执⾏的。
result = Array.prototype.slice.call(arguments);
}
document.head.append(script);
}
jsonp算起来应该就是通过script加载实现的跨域请求。其中重要的就是数据返回的接收,我们需要和后端开发同学协商回调函数的变量名。然后后端获取到回调函数名,并且在返回时把回调函数和数据拼接成字符串返回到前端。前端我们添加⼀个window对象的函数⽤于接收数据,在函数执⾏完成后,就会触发load事件,这样就可以真正执⾏⽤户回调函数了。可能你会觉得有点绕,其实细细的理⼀下,应该就明⽩了。
后端其实很简单,只要获取到jsonp函数变量名就可以了。然后把函数和数据拼接成字符串返回即可。
下⾯我们来看看Node.js中的实现:
let query = quest.query;
let jsonp = query.jsonp;//与后端协商的回调参数
ctx.body = jsonp+'({code:0,msg:"success"})';
这个回调函数并不是⽤户输⼊的successCallback,⽽是jsonp函数内部的window[callBackName] ,为什么要这样。你细想⼀下JavaScript的作⽤域应该就会知道。这就好⽐你在script标签中执⾏⼀个函数⼀样。
有可能我们第⼀次调⽤jsonp函数服务器会返回如下结果:
<script >
所以,得出结论就是:函数必须能通过window对象上访问到。不然执⾏时就会报错。这就是为什么我们不能直接把⽤户传⼊的回调直接⽤来当成回调接收数据的真正原因。
再次强调:JavaScript作⽤域。
⼀次成功的jsonp应该是:添加script标签到head,后端接收到jsonp数据,返回拼接好的函数名和数据字符串,执⾏window对象上的函数拿到数据,执⾏load事件,执⾏成功回调。
jsonp的abort⽅法何去何从
现在你已经知道了jsonp的原理了。那么如何才能对script加载数据进⾏abort呢。
犯难的问题来了,script并没有真正的abort⽅法给我们使⽤。我们所做的就是尽最⼤的努⼒提供类似于abort功能的⽅法。
思路就是使⽤Event事件对象。触发script的error监听事件。所以我们得对jsonp函数添加⼀个trigger辅助函数进⾏触发error事件。
//[trigger 触发事件]
function trigger(element,event){
if( !isString(event) ) {
return;
}
if ( element.dispatchEvent ){
let evt = ateEvent('Events');// initEvent接受3个参数
evt.initEvent(event, true, true);
element.dispatchEvent(evt);
}else if ( element.fireEvent ){ //IE
element.fireEvent('on'+event);
}else{
element['on'+event]();
}
}
let index = 0;
//仅供参考 jsonp.abort
function jsonp(url,jsonp,successCallback , errorCallback){
let script = ateElement('script');
let result ;
successCallback(result);
}
errorCallback('出错了');
}
let callBackName = 'jsonpCallback'+index++;
script.src=url+(url.indexOf('?') >=0 ? '&':'?')+jsonp+'='+callBackName;
window[callBackName]=function(){//拿给后端进⾏输出执⾏的。
result = Array.prototype.slice.call(arguments);
}
script.abort = ()=>{
return trigger(script,'error');
};
document.head.append(script);
return script;
}
我们把Promise也使⽤进来,那样的话,我们就可以脱离回调地狱了不是吗?
let index = 0;
//仅供参考 jsonp.abort
jquery实现ajax
function jsonp(url,query,jsonp){
let script = ateElement('script');
let result ;
let promise = new Promise(function(resolve,reject){
return resolve(result);
}
return reject('出错了');
}
let callBackName = 'jsonpCallback'+index++;
script.src=url+(url.indexOf('?') >=0 ? '&':'?')+jsonp+'='+callBackName;
document.head.append(script);
});
script.abort = ()=>{
return trigger(script,'error');
};
map.push({promise:promise,request:script});//创建promise和script之间的映射关系,保存到全局的⼀个数组中。
return promise;
}
同样的我们套⽤上⾯的xhr的abort函数封装。这样我们就⼤功告成了。基本的功能我们就全部实现了。我们就可以开始进⾏调⽤了。
let promise = jsonp('/test/getUserList','jsonp');
promise.then((result)=>{
console.log('成功了。', result);
},(error)=>{
console.log(error);
})
abort(promise);
总结
虽然,我们已经完成了封装,但是还有很多的意外没有考虑,要想再实战中运⽤还必须进⾏封装和重构。我们必须重视abort ⽅法在xhr/jsonp中的运⽤,但是也不能滥⽤,适可⽽⽌。存在多层服务器调⽤时,应该更需要慎重考虑。
好了,以上就是这篇⽂章的全部内容了,希望本⽂的内容对⼤家的学习或者⼯作能带来⼀定的帮助,如果有疑问⼤家可以留⾔交流,谢谢⼤家对的⽀持。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。