深入讲解xhr(XMLHttpRequest)/jsonp请求之abort(2)

let map = [];//用于保存promise和xhr之间的映射关系 //仅供参考 promise abort 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); }); 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 = document.createElement('script'); let result ; script.onload = function(){ successCallback(result); } script.onerror = function(){ 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对象的函数用于接收数据,在函数执行完成后,就会触发script.onload事件,这样就可以真正执行用户回调函数了。

可能你会觉得有点绕,其实细细的理一下,应该就明白了。

后端其实很简单,只要获取到jsonp函数变量名就可以了。然后把函数和数据拼接成字符串返回即可。

下面我们来看看Node.js中的实现:

let query = ctx.request.query; let jsonp = query.jsonp;//与后端协商的回调参数 ctx.body = jsonp+'({code:0,msg:"success"})';

这个回调函数并不是用户输入的successCallback,而是jsonp函数内部的window[callBackName] ,为什么要这样。你细想一下JavaScript的作用域应该就会知道。这就好比你在script标签中执行一个函数一样。

有可能我们第一次调用jsonp函数服务器会返回如下结果:

<script > //只有这一行是服务器返回的, //script标签是document.head.append(script)时候加的 jsonpCallback0({code:0,msg:"success"}); </script>

所以,得出结论就是:函数必须能通过window对象上访问到。不然执行时就会报错。这就是为什么我们不能直接把用户传入的回调直接用来当成回调接收数据的真正原因。

再次强调:JavaScript作用域。

一次成功的jsonp应该是:添加script标签到head,后端接收到jsonp数据,返回拼接好的函数名和数据字符串,执行window对象上的函数拿到数据,执行script.onload事件,执行成功回调。

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 = document.createEvent('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 = document.createElement('script'); let result ; script.onload = function(){ successCallback(result); } script.onerror = function(){ 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也使用进来,那样的话,我们就可以脱离回调地狱了不是吗?

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wyjggd.html