###一倔韭、前端進(jìn)行網(wǎng)絡(luò)請求的關(guān)注點(diǎn)
? ? 大多數(shù)情況下链蕊,在前端發(fā)起一個(gè)網(wǎng)絡(luò)請求我們只需關(guān)注下面幾點(diǎn):
傳入基本參數(shù)(url祥得,請求方式)
請求參數(shù)郁竟、請求參數(shù)類型
設(shè)置請求頭
獲取響應(yīng)的方式
獲取響應(yīng)頭、響應(yīng)狀態(tài)狡逢、響應(yīng)結(jié)果
異常處理
攜帶cookie設(shè)置
跨域請求
###二宁舰、前端進(jìn)行網(wǎng)絡(luò)請求的方式
form表單、ifream奢浑、刷新頁面
Ajax - 異步網(wǎng)絡(luò)請求的開山鼻祖
jQuery - 一個(gè)時(shí)代
fetch - Ajax的替代者
axios明吩、request等眾多開源庫
###三、關(guān)于網(wǎng)絡(luò)請求的疑問
Ajax的出現(xiàn)解決了什么問題
原生Ajax如何使用
jQuery的網(wǎng)絡(luò)請求方式
fetch的用法以及坑點(diǎn)
如何正確的使用fetch
如何選擇合適的跨域方式
? ? 帶著以上這些問題殷费、關(guān)注點(diǎn)我們對幾種網(wǎng)絡(luò)請求進(jìn)行一次全面的分析印荔。
####四、Ajax 的出現(xiàn)解決了什么問題
? ? 在Ajax出現(xiàn)之前详羡,web程序是這樣工作的:
? ? 這種交互的的缺陷是顯而易見的仍律,任何和服務(wù)器的交互都需要刷新頁面,用戶體驗(yàn)非常差实柠,Ajax的出現(xiàn)解決了這個(gè)問題水泉。Ajax全稱Asynchronous JavaScript + XML(異步JavaScript和XML)。
? ? 使用Ajax,網(wǎng)頁應(yīng)用能夠快速地將增量更新呈現(xiàn)在用戶界面上草则,而不需要重載(刷新)整個(gè)頁面钢拧。
? ? Ajax本身不是一種新技術(shù),而是用來描述一種使用現(xiàn)有技術(shù)集合實(shí)現(xiàn)的一個(gè)技術(shù)方案炕横,瀏覽器的XMLHttpRequest是實(shí)現(xiàn)Ajax最重要的對象(IE6以下使用ActiveXObject)源内。
? ? 盡管X在Ajax中代表XML, 但由于JSON的許多優(yōu)勢,比如更加輕量以及作為Javascript的一部分份殿,目前JSON的使用比XML更加普遍膜钓。
###五、原生 Ajax 的用法
? ? 這里主要分析XMLHttpRequest對象卿嘲,下面是它的一段基礎(chǔ)使用:
var xhr = new XMLHttpRequest();
? ? ? ? xhr.open('post','www.xxx.com',true)
? ? ? ? // 接收返回值
? ? ? ? xhr.onreadystatechange = function(){
? ? ? ? ? ? if(xhr.readyState === 4 ){
? ? ? ? ? ? ? ? if(xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
? ? ? ? ? ? ? ? ? ? console.log(xhr.responseText);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? // 處理請求參數(shù)
? ? ? ? postData = {"name1":"value1","name2":"value2"};
? ? ? ? postData = (function(value){
? ? ? ? var dataString = "";
? ? ? ? for(var key in value){
? ? ? ? ? ? dataString += key+"="+value[key]+"&";
? ? ? ? };
? ? ? ? ? return dataString;
? ? ? ? }(postData));
? ? ? ? // 設(shè)置請求頭
? ? ? ? xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
? ? ? ? // 異常處理
? ? ? ? xhr.onerror = function() {
? ? ? ? ? console.log('Network request failed')
? ? ? ? }
? ? ? ? // 跨域攜帶 cookie
? ? ? ? xhr.withCredentials = true;
? ? ? ? // 發(fā)出請求
? ? ? ? xhr.send(postData);
? ? 下面分別對XMLHttpRequest對象常用的的函數(shù)颂斜、屬性、事件進(jìn)行分析拾枣。
##函數(shù)
###open
用于初始化一個(gè)請求沃疮,用法:
xhr.open(method, url, async);
method:請求方式,如get梅肤、post
url:請求的url
async:是否為異步請求
###send
用于發(fā)送HTTP請求司蔬,即調(diào)用該方法后HTTP請求才會(huì)被真正發(fā)出,用法:
xhr.send(param)
param:http 請求的參數(shù)凭语,可以為string葱她、Blob等類型撩扒。
###abort
用于終止一個(gè)ajax請求似扔,調(diào)用此方法后readyState將被設(shè)置為0,用法:
xhr.abort()
setRequestHeader
用于設(shè)置HTTP請求頭搓谆,此方法必須在open()方法和send()之間調(diào)用炒辉,用法:
xhr.setRequestHeader(header, value);
getResponseHeader
用于獲取http返回頭,如果在返回頭中有多個(gè)一樣的名稱泉手,那么返回的值就會(huì)是用逗號和空格將值分隔的字符串黔寇,用法:
var header = xhr.getResponseHeader(name);
##屬性
###readyState
? ? 用來標(biāo)識(shí)當(dāng)前XMLHttpRequest對象所處的狀態(tài),XMLHttpRequest對象總是位于下列狀態(tài)中的一個(gè):
###status
? ? 表示http請求的狀態(tài), 初始值為0斩萌。如果服務(wù)器沒有顯式地指定狀態(tài)碼, 那么status將被設(shè)置為默認(rèn)值, 即200缝裤。
###responseType
? ? 表示響應(yīng)的數(shù)據(jù)類型,并允許我們手動(dòng)設(shè)置颊郎,如果為空憋飞,默認(rèn)為text類型,可以有下面的取值:
###response
? ? 返回響應(yīng)的正文姆吭,返回的類型由上面的responseType決定榛做。
###withCredentials
? ? ajax請求默認(rèn)會(huì)攜帶同源請求的cookie,而跨域請求則不會(huì)攜帶cookie,設(shè)置xhr的withCredentials的屬性為true將允許攜帶跨域cookie检眯。
##事件回調(diào)
###onreadystatechange
xhr.onreadystatechange = callback;
當(dāng)readyState屬性發(fā)生變化時(shí)厘擂,callback 會(huì)被觸發(fā)。
###onloadstart
xhr.onloadstart = callback;
在ajax請求發(fā)送之前(readyState==1后, readyState==2前)锰瘸,callback會(huì)被觸發(fā)刽严。
###onprogress
xhr.onprogress = function(event){
? console.log(event.loaded / event.total);
}
回調(diào)函數(shù)可以獲取資源總大小total,已經(jīng)加載的資源大小loaded获茬,用這兩個(gè)值可以計(jì)算加載進(jìn)度港庄。
###onload
xhr.onload = callback;
當(dāng)一個(gè)資源及其依賴資源已完成加載時(shí),將觸發(fā)callback恕曲,通常我們會(huì)在onload事件中處理返回值鹏氧。
##異常處理
###onerror
xhr.onerror = callback;
當(dāng)ajax資源加載失敗時(shí)會(huì)觸發(fā)callback。
###ontimeout
xhr.ontimeout = callback;
當(dāng)進(jìn)度由于預(yù)定時(shí)間到期而終止時(shí)佩谣,會(huì)觸發(fā)callback把还,超時(shí)時(shí)間可使用timeout屬性進(jìn)行設(shè)置。
##六茸俭、jQuery 對 Ajax 的封裝
? ? 在很長一段時(shí)間里吊履,人們使用jQuery提供的ajax封裝進(jìn)行網(wǎng)絡(luò)請求,包括$.ajax调鬓、$.get艇炎、$.post等,這幾個(gè)方法放到現(xiàn)在腾窝,我依然覺得很實(shí)用缀踪。
$.ajax({
? ? dataType: 'json', // 設(shè)置返回值類型
? ? contentType: 'application/json', // 設(shè)置參數(shù)類型
? ? headers: {'Content-Type','application/json'},// 設(shè)置請求頭
? ? xhrFields: { withCredentials: true }, // 跨域攜帶 cookie
? ? data: JSON.stringify({a: [{b:1, a:1}]}), // 傳遞參數(shù)
? ? error:function(xhr,status){? // 錯(cuò)誤處理
? ? ? console.log(xhr,status);
? ? },
? ? success: function (data,status) {? // 獲取結(jié)果
? ? ? console.log(data,status);
? ? }
})
$.ajax只接收一個(gè)參數(shù),這個(gè)參數(shù)接收一系列配置虹脯,其自己封裝了一個(gè)jqXHR對象驴娃,有興趣可以閱讀一下 jQuary-ajax 源碼:
https://github.com/jquery/jquery/blob/master/src/ajax.js
常用配置:
###url
當(dāng)前頁地址。發(fā)送請求的地址循集。
###type
類型:String 請求方式 ("POST" 或"GET")唇敞, 默認(rèn)為 "GET"。注意:其它HTTP請求方法咒彤,如PUT和 DELETE也可以使用疆柔,但僅部分瀏覽器支持。
###timeout
類型:Number設(shè)置請求超時(shí)時(shí)間(毫秒)镶柱。此設(shè)置將覆蓋全局設(shè)置旷档。
###success
類型:Function 請求成功后的回調(diào)函數(shù)。
###jsonp
在一個(gè)jsonp請求中重寫回調(diào)函數(shù)的名字奸例。這個(gè)值用來替代在"callback=?"這種GET或POST請求中URL參數(shù)里的"callback"部分彬犯。
###error
類型:Function 向楼,請求失敗時(shí)調(diào)用此函數(shù)。
注意:源碼里對錯(cuò)誤的判定:
isSuccess = status >= 200 && status < 300 || status === 304;
返回值除了這幾個(gè)狀態(tài)碼都會(huì)進(jìn)error回調(diào)谐区。
### dataType
"xml": 返回 XML 文檔湖蜕,可用 jQuery 處理。
"html": 返回純文本 HTML 信息宋列;包含的 script 標(biāo)簽會(huì)在插入 dom 時(shí)執(zhí)行昭抒。
"script": 返回純文本 JavaScript 代碼。不會(huì)自動(dòng)緩存結(jié)果炼杖。除非設(shè)置了 "cache" 參數(shù)灭返。注意:在遠(yuǎn)程請求時(shí) (不在同一個(gè)域下),所有 POST 請求都將轉(zhuǎn)為 GET 請求坤邪。(因?yàn)閷⑹褂?DOM 的 script 標(biāo)簽來加載)
"json": 返回 JSON 數(shù)據(jù) 熙含。
"jsonp": JSONP 格式。使用 JSONP 形式調(diào)用函數(shù)時(shí)艇纺,如 "myurl?callback=?" jQuery 將自動(dòng)替換 ? 為正確的函數(shù)名怎静,以執(zhí)行回調(diào)函數(shù)。
"text": 返回純文本字符串
data
類型:String 使用JSON.stringify轉(zhuǎn)碼黔衡。
###complete
類型:Function請求完成后回調(diào)函數(shù) (請求成功或失敗之后均調(diào)用)蚓聘。
###async
類型:Boolean 默認(rèn)值:true。默認(rèn)設(shè)置下盟劫,所有請求均為異步請求夜牡。如果需要發(fā)送同步請求,請將此選項(xiàng)設(shè)置為 false侣签。
###contentType
類型:String默認(rèn)值: "application/x-www-form-urlencoded"塘装。發(fā)送信息至服務(wù)器時(shí)內(nèi)容編碼類型。
鍵值對這樣組織在一般的情況下是沒有什么問題的硝岗,這里說的一般是氢哮,不帶嵌套類型JSON袋毙,也就是 簡單的JSON型檀,形如這樣:
{
? ? a: 1,
? ? b: 2,
? ? c: 3
}
? ? 但是在一些復(fù)雜的情況下就有問題了。 例如在 Ajax中你要傳一個(gè)復(fù)雜的 json 對像听盖,也就說是對象嵌數(shù)組胀溺,數(shù)組中包括對象,你這樣傳:application/x-www-form-urlencoded 這種形式是沒有辦法將復(fù)雜的JSON組織成鍵值對形式皆看。
{
? data: {
? ? a: [{
? ? ? x: 2
? ? }]
? }
}
可以用如下方式傳遞復(fù)雜的json對象:
$.ajax({
? ? dataType: 'json',
? ? contentType: 'application/json',
? ? data: JSON.stringify({a: [{b:1, a:1}]})
})
##七仓坞、jQuery 的替代者
? ? 近年來前端MV*的發(fā)展壯大,人們越來越少的使用jQuery腰吟,我們不可能單獨(dú)為了使用jQuery的Ajax api來單獨(dú)引入他无埃,無可避免的徙瓶,我們需要尋找新的技術(shù)方案。
? ? 尤雨溪在他的文檔中推薦大家用axios進(jìn)行網(wǎng)絡(luò)請求嫉称。axios基于Promise對原生的XHR進(jìn)行了非常全面的封裝侦镇,使用方式也非常的優(yōu)雅。另外织阅,axios同樣提供了在node環(huán)境下的支持壳繁,可謂是網(wǎng)絡(luò)請求的首選方案。
? ? 未來必定還會(huì)出現(xiàn)更優(yōu)秀的封裝荔棉,他們有非常周全的考慮以及詳細(xì)的文檔闹炉,這里我們不多做考究,我們把關(guān)注的重點(diǎn)放在更底層的 APIfetch润樱。
? ? Fetch API是一個(gè)用用于訪問和操縱 HTTP 管道的強(qiáng)大的原生 API渣触。
? ? 這種功能以前是使用? XMLHttpRequest 實(shí)現(xiàn)的。Fetch 提供了一個(gè)更好的替代方法壹若,可以很容易地被其他技術(shù)使用昵观,例如 Service Workers。Fetch 還提供了單個(gè)邏輯位置來定義其他 HTTP 相關(guān)概念舌稀,例如 CORS 和 HTTP 的擴(kuò)展啊犬。
? ? 可見fetch是作為XMLHttpRequest的替代品出現(xiàn)的。
? ? 使用fetch壁查,你不需要再額外加載一個(gè)外部資源觉至。但它還沒有被瀏覽器完全支持,所以你仍然需要一個(gè)polyfill睡腿。
##八语御、fetch 的使用
? ? 一個(gè)基本的 fetch 請求:
const options = {
? ? method: "POST", // 請求參數(shù)
? ? headers: { "Content-Type": "application/json"}, // 設(shè)置請求頭
? ? body: JSON.stringify({name:'123'}), // 請求參數(shù)
? ? credentials: "same-origin", // cookie 設(shè)置
? ? mode: "cors", // 跨域
}
fetch('http://www.xxx.com')
? .then(function(response) {
? ? return response.json();
? })
? .then(function(myJson) {
? ? console.log(myJson); // 響應(yīng)數(shù)據(jù)
? })
? .catch(function(err){
? ? console.log(err); // 異常處理
? })
? ? Fetch API提供了一個(gè)全局的fetch()方法,以及幾個(gè)輔助對象來發(fā)起一個(gè)網(wǎng)絡(luò)請求席怪。
###fetch()
fetch()方法用于發(fā)起獲取資源的請求应闯。它返回一個(gè)promise,這個(gè) promise 會(huì)在請求響應(yīng)后被 resolve挂捻,并傳回 Response 對象碉纺。
###Headers
可以通過Headers()構(gòu)造函數(shù)來創(chuàng)建一個(gè)你自己的headers對象,相當(dāng)于 response/request 的頭信息刻撒,可以使你查詢到這些頭信息骨田,或者針對不同的結(jié)果做不同的操作。
var myHeaders = new Headers();
myHeaders.append("Content-Type", "text/plain");
###Request
通過Request()構(gòu)造函數(shù)可以創(chuàng)建一個(gè)Request對象声怔,這個(gè)對象可以作為fetch函數(shù)的第二個(gè)參數(shù)态贤。
###Response
在fetch()處理完promises之后返回一個(gè)Response實(shí)例,也可以手動(dòng)創(chuàng)建一個(gè)Response實(shí)例醋火。
##九悠汽、fetch polyfill 源碼分析
? ? 由于fetch是一個(gè)非常底層的API箱吕,所以我們無法進(jìn)一步的探究它的底層,但是我們可以借助它的polyfill探究它的基本原理柿冲,并找出其中的坑點(diǎn)殖氏。
###代碼結(jié)構(gòu)
? ? 由代碼可見,polyfill主要對Fetch API 提供的四大對象進(jìn)行了封裝:
###fetch封裝
代碼非常清晰:
構(gòu)造一個(gè)Promise對象并返回姻采;
創(chuàng)建一個(gè)Request對象雅采;
創(chuàng)建一個(gè)XMLHttpRequest對象;
取出Request對象中的請求url慨亲,請求方發(fā)婚瓜,open一個(gè)xhr請求,并將Request對象中存儲(chǔ)的headers取出賦給 xhr刑棵;
xhr onload后取出response的status巴刻、headers、body封裝Response對象蛉签,調(diào)用resolve胡陪。
###異常處理
可以發(fā)現(xiàn),調(diào)用reject有三種可能:
1.請求超時(shí)
2.請求失敗
? ? 注意:當(dāng)和服務(wù)器建立簡介碍舍,并收到服務(wù)器的異常狀態(tài)碼如404柠座、500等并不能觸發(fā)onerror。當(dāng)網(wǎng)絡(luò)故障時(shí)或請求被阻止時(shí)片橡,才會(huì)標(biāo)記為 reject妈经,如跨域、url不存在捧书,網(wǎng)絡(luò)異常等會(huì)觸發(fā)onerror吹泡。
? .所以使用 fetch 當(dāng)接收到異常狀態(tài)碼都是會(huì)進(jìn)入 then 而不是 catch。這些錯(cuò)誤請求往往要手動(dòng)處理经瓷。
3.手動(dòng)終止
? ? 可以在request參數(shù)中傳入signal對象爆哑,并對signal對象添加abort事件監(jiān)聽,當(dāng)xhr.readyState變?yōu)?(響應(yīng)內(nèi)容解析完成)后將 signal 對象的 abort 事件監(jiān)聽移除掉舆吮。
? ? 這表示揭朝,在一個(gè)fetch請求結(jié)束之前可以調(diào)用signal.abort將其終止。在瀏覽器中可以使用AbortController()構(gòu)造函數(shù)創(chuàng)建一個(gè)控制器歪泳,然后使用AbortController.signal屬性萝勤。
? ? 這是一個(gè)實(shí)驗(yàn)中的功能露筒,此功能某些瀏覽器尚在開發(fā)中呐伞。
###Headers 封裝
? ? 在 header 對象中維護(hù)了一個(gè)map對象,構(gòu)造函數(shù)中可以傳入Header對象慎式、數(shù)組伶氢、普通對象類型的header趟径,并將所有的值維護(hù)到map中。
? ? 之前在fetch函數(shù)中看到調(diào)用了header的forEach方法癣防,下面是它的實(shí)現(xiàn):
? ? 可見header的遍歷即其內(nèi)部map的遍歷蜗巧。
? ? 另外Header還提供了append、delete蕾盯、get幕屹、set等方法,都是對其內(nèi)部的map對象進(jìn)行操作级遭。
###Request 對象
? ? Request對象接收的兩個(gè)參數(shù)即fetch函數(shù)接收的兩個(gè)參數(shù)望拖,第一個(gè)參數(shù)可以直接傳遞url,也可以傳遞一個(gè)構(gòu)造好的request對象挫鸽。第二個(gè)參數(shù)即控制不同配置的option對象说敏。
? ? 可以傳入credentials、headers丢郊、method盔沫、mode、signal枫匾、referrer等屬性架诞。
? ? 這里注意:傳入的headers被當(dāng)作Headers構(gòu)造函數(shù)的參數(shù)來構(gòu)造 header 對象。
###cookie 處理
? ? fetch 函數(shù)中還有如下的代碼:
if (request.credentials === 'include') {
? ? ? xhr.withCredentials = true
? ? } else if (request.credentials === 'omit') {
? ? ? xhr.withCredentials = false
? ? }
? ? 默認(rèn)的credentials類型為same-origin, 即可攜帶同源請求的 coodkie干茉。
? ? 然后我發(fā)現(xiàn)這里 polyfill 的實(shí)現(xiàn)和 MDN- 使用 Fetch(https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch) 以及很多資料是不一致的:
? ? mdn: 默認(rèn)情況下侈贷,fetch 不會(huì)從服務(wù)端發(fā)送或接收任何 cookies
? ? 于是我分別實(shí)驗(yàn)了下使用polyfill和使用原生fetch攜帶 cookie 的情況,發(fā)現(xiàn)在不設(shè)置credentials的情況下居然都是默認(rèn)攜帶同源cookie的等脂,這和文檔的說明說不一致的俏蛮,查閱了許多資料后都是說fetch默認(rèn)不會(huì)攜帶 cookie,下面是使用原生fetch在瀏覽器進(jìn)行請求的情況:
? ? 然后我發(fā)現(xiàn)在 MDN-Fetch-Request(https://developer.mozilla.org/zh-CN/docs/Web/API/Request/credentials) 已經(jīng)指出新版瀏覽器credentials默認(rèn)值已更改為same-origin上遥,舊版依然是omit搏屑。
? ? 確實(shí) MDN- 使用 Fetch 這里的文檔更新的有些不及時(shí),誤人子弟了:
https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch
###Response 對象
? ? Response對象是fetch調(diào)用成功后的返回值:
? ? 回顧下fetch中對Response`的操作:
xhr.onload = function () {
? ? ? var options = {
? ? ? ? status: xhr.status,
? ? ? ? statusText: xhr.statusText,
? ? ? ? headers: parseHeaders(xhr.getAllResponseHeaders() || '')
? ? ? }
? ? ? options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL')
? ? ? var body = 'response' in xhr ? xhr.response : xhr.responseText
? ? ? resolve(new Response(body, options))
? ? }
Response構(gòu)造函數(shù):
? ? 可見在構(gòu)造函數(shù)中主要對options中的status粉楚、statusText辣恋、headers、url等分別做了處理并掛載到Response對象上模软。
? ? 構(gòu)造函數(shù)里面并沒有對responseText的明確處理伟骨,最后交給了_initBody函數(shù)處理,而Response并沒有主動(dòng)聲明_initBody屬性燃异,代碼最后使用Response調(diào)用了Body函數(shù)携狭,實(shí)際上_initBody函數(shù)是通過Body函數(shù)掛載到Response身上的,先來看看_initBody函數(shù):
? ? 可見回俐,_initBody函數(shù)根據(jù)xhr.response的類型(Blob逛腿、FormData稀并、String...),為不同的參數(shù)進(jìn)行賦值单默,這些參數(shù)在Body方法中得到不同的應(yīng)用碘举,下面具體看看Body函數(shù)還做了哪些其他的操作:
? ? Body函數(shù)中還為Response對象掛載了四個(gè)函數(shù),text搁廓、json引颈、blob、formData境蜕,這些函數(shù)中的操作就是將 _initBody 中得到的不同類型的返回值返回线欲。
? ? 這也說明了,在fetch執(zhí)行完畢后汽摹,不能直接在response中獲取到返回值而必須調(diào)用text()李丰、json()等函數(shù)才能獲取到返回值。
? ? 這里還有一點(diǎn)需要說明:幾個(gè)函數(shù)中都有類似下面的邏輯:
var rejected = consumed(this)
? ? if (rejected) {
? ? ? return rejected
? ? }
consumed 函數(shù):
function consumed(body) {
? if (body.bodyUsed) {
? ? return Promise.reject(new TypeError('Already read'))
? }
? body.bodyUsed = true
}
? ? 每次調(diào)用text()逼泣、json()等函數(shù)后會(huì)將bodyUsed變量變?yōu)閠rue趴泌,用來標(biāo)識(shí)返回值已經(jīng)讀取過了,下一次再讀取直接拋出TypeError('Already read')拉庶。這也遵循了原生fetch的原則:
? ? 因?yàn)?Responses 對象被設(shè)置為了 stream 的方式嗜憔,所以它們只能被讀取一次。
##十氏仗、fetch 的坑點(diǎn)
? ? VUE的文檔中對fetch有下面的描述:
? ? 使用fetch還有很多別的注意事項(xiàng)吉捶,這也是為什么大家現(xiàn)階段還是更喜歡axios 多一些。當(dāng)然這個(gè)事情在未來可能會(huì)發(fā)生改變皆尔。
? ? 由于fetch是一個(gè)非常底層的API呐舔,它并沒有被進(jìn)行很多封裝,還有許多問題需要處理:
不能直接傳遞JavaScript對象作為參數(shù)慷蠕;
需要自己判斷返回值類型珊拼,并執(zhí)行響應(yīng)獲取返回值的方法;
獲取返回值方法只能調(diào)用一次流炕,不能多次調(diào)用澎现;
無法正常的捕獲異常;
老版瀏覽器不會(huì)默認(rèn)攜帶cookie每辟;
不支持jsonp剑辫。
##十一、對 fetch 的封裝
###請求參數(shù)處理
? ? 支持傳入不同的參數(shù)類型:
function stringify(url, data) {
? var dataString = url.indexOf('?') == -1 ? '?' : '&';
? for (var key in data) {
? ? dataString += key + '=' + data[key] + '&';
? };
? return dataString;
}
if (request.formData) {
? request.body = request.data;
} else if (/^get$/i.test(request.method)) {
? request.url = `${request.url}${stringify(request.url, request.data)}`;
} else if (request.form) {
? request.headers.set('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8');
? request.body = stringify(request.data);
} else {
? request.headers.set('Content-Type', 'application/json;charset=UTF-8');
? request.body = JSON.stringify(request.data);
}
###cookie 攜帶
? ? fetch在新版瀏覽器已經(jīng)開始默認(rèn)攜帶同源cookie渠欺,但在老版瀏覽器中不會(huì)默認(rèn)攜帶妹蔽,我們需要對他進(jìn)行統(tǒng)一設(shè)置:
request.credentials =? 'same-origin'; // 同源攜帶
request.credentials =? 'include'; // 可跨域攜帶
###異常處理
? ? 當(dāng)接收到一個(gè)代表錯(cuò)誤的 HTTP 狀態(tài)碼時(shí),從 fetch() 返回的 Promise 不會(huì)被標(biāo)記為 reject, 即使該 HTTP 響應(yīng)的狀態(tài)碼是 404 或 500讹开。相反盅视,它會(huì)將 Promise 狀態(tài)標(biāo)記為 resolve (但是會(huì)將 resolve 的返回值的 ok 屬性設(shè)置為 false )捐名,僅當(dāng)網(wǎng)絡(luò)故障時(shí)或請求被阻止時(shí)狡汉,才會(huì)標(biāo)記為 reject修己。
? ? 因此我們要對fetch的異常進(jìn)行統(tǒng)一處理:
.then(response => {sp
? if (response.ok) {
? ? return Promise.resolve(response);
? }else{
? ? const error = new Error(`請求失敗! 狀態(tài)碼: ${response.status}, 失敗信息: ${response.statusText}`);
? ? error.response = response;
? ? return Promise.reject(error);
? }
});
###返回值處理
? ? 對不同的返回值類型調(diào)用不同的函數(shù)接收,這里必須提前判斷好類型,不能多次調(diào)用獲取返回值的方法:
.then(response => {
? let contentType = response.headers.get('content-type');
? if (contentType.includes('application/json')) {
? ? return response.json();
? } else {
? ? return response.text();
? }
});
###jsonp
? ? fetch本身沒有提供對jsonp的支持洞渤,jsonp本身也不屬于一種非常好的解決跨域的方式,推薦使用cors或者nginx解決跨域拉背,具體請看下面的章節(jié)住闯。
? ? fetch 封裝好了,可以愉快的使用了拂酣。
? ? 嗯秋冰,axios 真好用。
##十二婶熬、跨域總結(jié)
? ? 談到網(wǎng)絡(luò)請求剑勾,就不得不提跨域。
? ? 瀏覽器的同源策略限制了從同一個(gè)源加載的文檔或腳本如何與來自另一個(gè)源的資源進(jìn)行交互赵颅。這是一個(gè)用于隔離潛在惡意文件的重要安全機(jī)制虽另。通常不允許不同源間的讀操作。
? ? 跨域條件:協(xié)議饺谬,域名捂刺,端口,有一個(gè)不同就算跨域募寨。
? ? 下面是解決跨域的幾種方式:
###nginx
? ? 使用nginx反向代理實(shí)現(xiàn)跨域族展,參考我這篇文章:前端開發(fā)者必備的 nginx 知識(shí)。
###cors
? ? CORS是一個(gè)W3C標(biāo)準(zhǔn)拔鹰,全稱是"跨域資源共享"(Cross-origin resource sharing)苛谷。它允許瀏覽器向跨源服務(wù)器,發(fā)出XMLHttpRequest請求格郁。
? ? 服務(wù)端設(shè)置Access-Control-Allow-Origin就可以開啟CORS腹殿。 該屬性表示哪些域名可以訪問資源,如果設(shè)置通配符則表示所有網(wǎng)站都可以訪問資源例书。
app.all('*', function (req, res, next) {
? ? res.header("Access-Control-Allow-Origin", "*");
? ? res.header("Access-Control-Allow-Headers", "X-Requested-With");
? ? res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
? ? next();
});
###jsonp
? ? script標(biāo)簽的src屬性中的鏈接可以訪問跨域的js腳本锣尉,利用這個(gè)特性,服務(wù)端不再返回JSON格式的數(shù)據(jù)决采,而是返回一段調(diào)用某個(gè)函數(shù)的js代碼自沧,在src中進(jìn)行了調(diào)用,這樣實(shí)現(xiàn)了跨域。
? ? jquery對jsonp的支持:
$.ajax({
? ? ? ? ? ? type : "get",
? ? ? ? ? ? url : "http://xxxx"
? ? ? ? ? ? dataType: "jsonp",
? ? ? ? ? ? jsonp:"callback",
? ? ? ? ? ? jsonpCallback: "doo",
? ? ? ? ? ? success : function(data) {
? ? ? ? ? ? ? ? console.log(data);
? ? ? ? ? ? }
? ? ? ? });
? ? fetch拇厢、axios等并沒有直接提供對jsonp的支持爱谁,如果需要使用這種方式,我們可以嘗試進(jìn)行手動(dòng)封裝:
(function (window,document) {ow,document)
? ? "use strict";
? ? var jsonp = function (url,data,callback) {
? ? ? ? // 1. 將傳入的 data 數(shù)據(jù)轉(zhuǎn)化為 url 字符串形式
? ? ? ? // {id:1,name:'jack'} => id=1&name=jack
? ? ? ? var dataString = url.indexof('?') == -1? '?': '&';
? ? ? ? for(var key in data){
? ? ? ? ? ? dataString += key + '=' + data[key] + '&';
? ? ? ? };
? ? ? ? // 2 處理 url 中的回調(diào)函數(shù)
? ? ? ? // cbFuncName 回調(diào)函數(shù)的名字 :my_json_cb_ 名字的前綴 + 隨機(jī)數(shù)(把小數(shù)點(diǎn)去掉)
? ? ? ? var cbFuncName = 'my_json_cb_' + Math.random().toString().replace('.','');
? ? ? ? dataString += 'callback=' + cbFuncName;
? ? ? ? // 3. 創(chuàng)建一個(gè) script 標(biāo)簽并插入到頁面中
? ? ? ? var scriptEle = document.createElement('script');
? ? ? ? scriptEle.src = url + dataString;
? ? ? ? // 4. 掛載回調(diào)函數(shù)
? ? ? ? window[cbFuncName] = function (data) {
? ? ? ? ? ? callback(data);
? ? ? ? ? ? // 處理完回調(diào)函數(shù)的數(shù)據(jù)之后孝偎,刪除 jsonp 的 script 標(biāo)簽
? ? ? ? ? ? document.body.removeChild(scriptEle);
? ? ? ? }
? ? ? ? document.body.appendChild(scriptEle);
? ? }
? ? window.$jsonp = jsonp;
})(window,document)
###postMessage 跨域
? ? postMessage()方法允許來自不同源的腳本采用異步方式進(jìn)行有限的通信访敌,可以實(shí)現(xiàn)跨文本檔、多窗口衣盾、跨域消息傳遞寺旺。
// 捕獲 iframe
var domain = 'http://scriptandstyle.com';
var iframe = document.getElementById('myIFrame').contentWindow;
// 發(fā)送消息
setInterval(function(){
? ? var message = 'Hello!? The time is: ' + (new Date().getTime());
? ? console.log('blog.local:? sending message:? ' + message);
? ? ? ? //send the message and target URI
? ? iframe.postMessage(message,domain);
},6000);
// 響應(yīng)事件
window.addEventListener('message',function(event) {
? ? if(event.origin !== 'http://davidwalsh.name') return;
? ? console.log('message received:? ' + event.data,event);
? ? event.source.postMessage('holla back youngin!',event.origin);
},false);
? ? postMessage跨域適用于以下場景:同瀏覽器多窗口間跨域通信、iframe間跨域通信势决。
###WebSocket
? ? WebSocket 是一種雙向通信協(xié)議阻塑,在建立連接之后,WebSocket的 server與 client都能主動(dòng)向?qū)Ψ桨l(fā)送或接收數(shù)據(jù)而不受同源策略的限制果复。
function WebSocketTest(){
? ? ? ? ? ? if ("WebSocket" in window){
? ? ? ? ? ? ? alert("您的瀏覽器支持 WebSocket!");
? ? ? ? ? ? ? // 打開一個(gè) web socket
? ? ? ? ? ? ? var ws = new WebSocket("ws://localhost:3000/abcd");
? ? ? ? ? ? ? ws.onopen = function(){
? ? ? ? ? ? ? ? ? // Web Socket 已連接上陈莽,使用 send() 方法發(fā)送數(shù)據(jù)
? ? ? ? ? ? ? ? ? ws.send("發(fā)送數(shù)據(jù)");
? ? ? ? ? ? ? ? ? alert("數(shù)據(jù)發(fā)送中...");
? ? ? ? ? ? ? };
? ? ? ? ? ? ? ws.onmessage = function (evt) {
? ? ? ? ? ? ? ? ? var received_msg = evt.data;
? ? ? ? ? ? ? ? ? alert("數(shù)據(jù)已接收...");
? ? ? ? ? ? ? };
? ? ? ? ? ? ? ws.onclose = function(){
? ? ? ? ? ? ? ? ? // 關(guān)閉 websocket
? ? ? ? ? ? ? ? ? alert("連接已關(guān)閉...");
? ? ? ? ? ? ? };
? ? ? ? ? ? } else{
? ? ? ? ? ? ? // 瀏覽器不支持 WebSocket
? ? ? ? ? ? ? alert("您的瀏覽器不支持 WebSocket!");
? ? ? ? ? ? }
? ? ? ? }