前言
創(chuàng)新互聯(lián)公司主營黔西網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營網(wǎng)站建設(shè)方案,app軟件開發(fā),黔西h5成都微信小程序搭建,黔西網(wǎng)站營銷推廣歡迎黔西等地區(qū)企業(yè)咨詢
相信大家在工作中經(jīng)常需要使用AJAX,所以當(dāng)大家看到文章標(biāo)題的時候可能會覺得這是一個老生常談的話題。
前端開發(fā)中向后端發(fā)起xhr(XMLHttpRequest)請求(代表性的就是熟悉的ajax)是再正常不過的事。
但在前端開發(fā)過程中,不怎么重視xhr的abort(中止掉xhr請求,及表示取消本次請求)。往往會帶來一些不可意料的結(jié)果。
比如:切換tab,發(fā)起xhr請求,渲染同一個列表。就這么簡單的拉取數(shù)據(jù)渲染列表的功能,并且可以根據(jù)tab切換。想想應(yīng)該是很簡單。但是假如你只顧著發(fā)起xhr請求,而沒有abort掉它,想想會發(fā)生什么。很有可能就是當(dāng)前選中的tab數(shù)據(jù),并不是你想要的。說白了就是數(shù)據(jù)錯了。這時候你可能就要考慮是不是xhr請求返回數(shù)據(jù)的順序問題。
答案是肯定的,xhr請求返回數(shù)據(jù)順序是不固定的。所以你要做的就是abort掉你之前的xhr請求,然后再發(fā)起一個新的xhr請求。
結(jié)合上面所說的例子可以知道xhr使用不當(dāng)會存在以下問題:
為了解決上面的問題,我們在進(jìn)行頁面的時候就必須考慮abort掉所有的xhr請求。
那么如何實(shí)現(xiàn)xhr的abort方法呢,或者通過何種方式abort掉xhr呢?
一個簡單的xhr
我們都知道,現(xiàn)在的框架(例如:jQuery的ajax模塊)對xhr都進(jìn)行了封裝,是為了讓我們更好的使用xhr。但是也蒙蔽了我們的眼睛。讓我們拋開框架,來看看一個簡單的xhr怎么實(shí)現(xiàn)。
//僅供參考 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請求的實(shí)現(xiàn),我把它命名為ajax,我們現(xiàn)在可以通過以下方式進(jìn)行調(diào)用:
ajax('get','/test/getUserList' , undefined , function(result){ console.log('成功了。', result); } ,function(error){ console.log(error); });
如果使用這個方法我們是沒辦法abort掉xhr請求的。好吧,現(xiàn)在我們把它改造一下,讓它支持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實(shí)例對象 }
好像沒有什么變化對吧。不錯,只要在函數(shù)的末尾添加return xhr;
將XMLHttpRequest實(shí)例對象返回即可。那我們在就已經(jīng)可以如愿的abort掉xhr請求。
let xhr = ajax('get','/test/getUserList' , undefined , function(result){ console.log('成功了。', result); } ,function(error){ console.log(error); }); //abort xhr.abort();
好像我們已經(jīng)大功告成了。但是問題來了,現(xiàn)在Promise這么好用,為什么不把它加進(jìn)來呢。像這樣沒法在我們的Promise鏈?zhǔn)秸{(diào)用上使用它。
Promise封裝xhr
好了,現(xiàn)在的首要任務(wù)是封裝出一個Promise版的ajax庫。首要要確認(rèn)的就是,ajax方法需要返回的是Promise實(shí)例對象,而不再是原生的XMLHttpRequest實(shí)例對象。知道了這一點(diǎn)那就可以進(jìn)行封裝了。
//僅供參考 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實(shí)例對象 }
使用了Promise之后我們不再需要傳入回調(diào)函數(shù)。所以參數(shù)減少了。這樣我們就可以愉快的進(jìn)行鏈?zhǔn)秸{(diào)用了。
let promise = ajax('get','/test/getUserList'); promise.then((result)=>{ console.log('成功了。', result); },(error)=>{ console.log(error); })
可問題又來了,Promise實(shí)例是沒有abort方法的。假如我們把a(bǔ)jax方法修改為返回xhr,我們是可以如期調(diào)用abort方法殺死請求,但是我們就不能使用Promise帶給我們的好處了。
仔細(xì)思考,最后一句return promise;
這里是不能改。我們只能另外想辦法。
最簡單的解決方式就是創(chuàng)建一個xhr和promise的映射關(guān)系。也就是每一個promise對應(yīng)一個唯一的xhr請求。有了思路之后,解決方案就來了。
let map = [];//用于保存promise和xhr之間的映射關(guān)系 //僅供參考 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});//創(chuàng)建promise和xhr之間的映射關(guān)系,保存到全局的一個數(shù)組中。 return promise;//返回Promise實(shí)例對象 } //abort 請求 function abort(promise){ for(let i = 0 ; i < map.length ; i++ ){ if ( map[i].promise === promise ){ map[i].request.abort(); } } }
通過在全局創(chuàng)建一個map保存所有的promise和xhr之間的映射關(guān)系。這樣我們就可以在需要abort請求的時候根據(jù)映射關(guān)系找到xhr并abort請求。
let promise = ajax('get','/test/getUserList'); promise.then((result)=>{ console.log('成功了。', result); },(error)=>{ console.log(error); }) abort(promise);
好吧,到這里Promise版的ajax,我們已經(jīng)實(shí)現(xiàn)了。是不是很簡單啊。
何為jsonp
假如你還不明白jsonp是何物,那希望下面的篇幅能讓你明白。可能你零星的知道跨越請求,但是可能沒有在實(shí)戰(zhàn)中碰到過。那么我們先來看看,一個簡單的jsonp函數(shù)是怎么實(shí)現(xiàn)的吧。
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(){//拿給后端進(jìn)行輸出執(zhí)行的。 result = Array.prototype.slice.call(arguments); } document.head.append(script); }
jsonp算起來應(yīng)該就是通過script加載實(shí)現(xiàn)的跨域請求。其中重要的就是數(shù)據(jù)返回的接收,我們需要和后端開發(fā)同學(xué)協(xié)商回調(diào)函數(shù)的變量名。然后后端獲取到回調(diào)函數(shù)名,并且在返回時把回調(diào)函數(shù)和數(shù)據(jù)拼接成字符串返回到前端。前端我們添加一個window對象的函數(shù)用于接收數(shù)據(jù),在函數(shù)執(zhí)行完成后,就會觸發(fā)script.onload事件,這樣就可以真正執(zhí)行用戶回調(diào)函數(shù)了。
可能你會覺得有點(diǎn)繞,其實(shí)細(xì)細(xì)的理一下,應(yīng)該就明白了。
后端其實(shí)很簡單,只要獲取到j(luò)sonp函數(shù)變量名就可以了。然后把函數(shù)和數(shù)據(jù)拼接成字符串返回即可。
下面我們來看看Node.js中的實(shí)現(xiàn):
let query = ctx.request.query; let jsonp = query.jsonp;//與后端協(xié)商的回調(diào)參數(shù) ctx.body = jsonp+'({code:0,msg:"success"})';
這個回調(diào)函數(shù)并不是用戶輸入的successCallback,而是jsonp函數(shù)內(nèi)部的window[callBackName]
,為什么要這樣。你細(xì)想一下JavaScript的作用域應(yīng)該就會知道。這就好比你在script標(biāo)簽中執(zhí)行一個函數(shù)一樣。
有可能我們第一次調(diào)用jsonp函數(shù)服務(wù)器會返回如下結(jié)果:
<script > //只有這一行是服務(wù)器返回的, //script標(biāo)簽是document.head.append(script)時候加的 jsonpCallback0({code:0,msg:"success"}); </script>
所以,得出結(jié)論就是:函數(shù)必須能通過window對象上訪問到。不然執(zhí)行時就會報錯。這就是為什么我們不能直接把用戶傳入的回調(diào)直接用來當(dāng)成回調(diào)接收數(shù)據(jù)的真正原因。
再次強(qiáng)調(diào):JavaScript作用域。
一次成功的jsonp應(yīng)該是:添加script標(biāo)簽到head,后端接收到j(luò)sonp數(shù)據(jù),返回拼接好的函數(shù)名和數(shù)據(jù)字符串,執(zhí)行window對象上的函數(shù)拿到數(shù)據(jù),執(zhí)行script.onload事件,執(zhí)行成功回調(diào)。
jsonp的abort方法何去何從
現(xiàn)在你已經(jīng)知道了jsonp的原理了。那么如何才能對script加載數(shù)據(jù)進(jìn)行abort呢。
犯難的問題來了,script并沒有真正的abort方法給我們使用。我們所做的就是盡最大的努力提供類似于abort功能的方法。
思路就是使用Event事件對象。觸發(fā)script的error監(jiān)聽事件。所以我們得對jsonp函數(shù)添加一個trigger輔助函數(shù)進(jìn)行觸發(fā)error事件。
//[trigger 觸發(fā)事件] function trigger(element,event){ if( !isString(event) ) { return; } if ( element.dispatchEvent ){ let evt = document.createEvent('Events');// initEvent接受3個參數(shù) 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(){//拿給后端進(jìn)行輸出執(zhí)行的。 result = Array.prototype.slice.call(arguments); } script.abort = ()=>{ return trigger(script,'error'); }; document.head.append(script); return script; }
我們把Promise也使用進(jìn)來,那樣的話,我們就可以脫離回調(diào)地獄了不是嗎?
let index = 0; //僅供參考 jsonp.abort function jsonp(url,query,jsonp){ let script = document.createElement('script'); let result ; let promise = new Promise(function(resolve,reject){ script.onload = function(){ return resolve(result); } script.onerror = function(){ return reject('出錯了'); } let callBackName = 'jsonpCallback'+index++; script.src=url+(url.indexOf('?') >=0 ? '&':'?')+jsonp+'='+callBackName; window[callBackName]=function(){//拿給后端進(jìn)行輸出執(zhí)行的。 result = Array.prototype.slice.call(arguments); } document.head.append(script); }); script.abort = ()=>{ return trigger(script,'error'); }; map.push({promise:promise,request:script});//創(chuàng)建promise和script之間的映射關(guān)系,保存到全局的一個數(shù)組中。 return promise; }
同樣的我們套用上面的xhr的abort函數(shù)封裝。這樣我們就大功告成了?;镜墓δ芪覀兙腿繉?shí)現(xiàn)了。我們就可以開始進(jìn)行調(diào)用了。
let promise = jsonp('/test/getUserList','jsonp'); promise.then((result)=>{ console.log('成功了。', result); },(error)=>{ console.log(error); }) abort(promise);
總結(jié)
雖然,我們已經(jīng)完成了封裝,但是還有很多的意外沒有考慮,要想再實(shí)戰(zhàn)中運(yùn)用還必須進(jìn)行封裝和重構(gòu)。我們必須重視abort方法在xhr/jsonp中的運(yùn)用,但是也不能濫用,適可而止。存在多層服務(wù)器調(diào)用時,應(yīng)該更需要慎重考慮。
要想了解更多,可以參考這是我封裝好的一個Promise版本的ajax/jsonp庫https://github.com/Yi-love/xhrp,大家也可以通過本地進(jìn)行下載。
好了,以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對創(chuàng)新互聯(lián)的支持。
網(wǎng)站題目:深入講解xhr(XMLHttpRequest)/jsonp請求之a(chǎn)bort
當(dāng)前URL:http://www.rwnh.cn/article42/ghciec.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站設(shè)計、網(wǎng)站策劃、營銷型網(wǎng)站建設(shè)、品牌網(wǎng)站制作、網(wǎng)站內(nèi)鏈、云服務(wù)器
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)