全面分析前端的網(wǎng)絡(luò)請求方式

圖片發(fā)自簡書App

###一倔韭、前端進(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程序是這樣工作的:

圖片發(fā)自簡書App

? ? 這種交互的的缺陷是顯而易見的仍律,任何和服務(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)行分析拾枣。

圖片發(fā)自簡書App

##函數(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è):

圖片發(fā)自簡書App


###status

? ? 表示http請求的狀態(tài), 初始值為0斩萌。如果服務(wù)器沒有顯式地指定狀態(tài)碼, 那么status將被設(shè)置為默認(rèn)值, 即200缝裤。


###responseType

? ? 表示響應(yīng)的數(shù)據(jù)類型,并允許我們手動(dòng)設(shè)置颊郎,如果為空憋飞,默認(rèn)為text類型,可以有下面的取值:

圖片發(fā)自簡書App

###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

圖片發(fā)自簡書App

常用配置:

###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ò)請求席怪。

圖片發(fā)自簡書App

###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)

圖片發(fā)自簡書App

? ? 由代碼可見,polyfill主要對Fetch API 提供的四大對象進(jìn)行了封裝:


###fetch封裝

圖片發(fā)自簡書App

代碼非常清晰:

構(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ā)自簡書App

可以發(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 封裝

圖片發(fā)自簡書App

? ? 在 header 對象中維護(hù)了一個(gè)map對象,構(gòu)造函數(shù)中可以傳入Header對象慎式、數(shù)組伶氢、普通對象類型的header趟径,并將所有的值維護(hù)到map中。

? ? 之前在fetch函數(shù)中看到調(diào)用了header的forEach方法癣防,下面是它的實(shí)現(xiàn):

圖片發(fā)自簡書App

? ? 可見header的遍歷即其內(nèi)部map的遍歷蜗巧。

? ? 另外Header還提供了append、delete蕾盯、get幕屹、set等方法,都是對其內(nèi)部的map對象進(jìn)行操作级遭。

###Request 對象

圖片發(fā)自簡書App

? ? 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ā)自簡書App

? ? 然后我發(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ù):

圖片發(fā)自簡書App

? ? 可見在構(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ù):

圖片發(fā)自簡書App

? ? 可見回俐,_initBody函數(shù)根據(jù)xhr.response的類型(Blob逛腿、FormData稀并、String...),為不同的參數(shù)進(jìn)行賦值单默,這些參數(shù)在Body方法中得到不同的應(yīng)用碘举,下面具體看看Body函數(shù)還做了哪些其他的操作:

圖片發(fā)自簡書App

? ? 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!");

? ? ? ? ? ? }

? ? ? ? }

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市虽抄,隨后出現(xiàn)的幾起案子走搁,更是在濱河造成了極大的恐慌,老刑警劉巖极颓,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件朱盐,死亡現(xiàn)場離奇詭異,居然都是意外死亡菠隆,警方通過查閱死者的電腦和手機(jī)兵琳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來骇径,“玉大人,你說我怎么就攤上這事破衔。” “怎么了晰筛?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長曙博。 經(jīng)常有香客問我怜瞒,道長父泳,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮惠窄,結(jié)果婚禮上蒸眠,老公的妹妹穿的比我還像新娘。我一直安慰自己杆融,他們只是感情好楞卡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著擒贸,像睡著了一般臀晃。 火紅的嫁衣襯著肌膚如雪觉渴。 梳的紋絲不亂的頭發(fā)上介劫,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天,我揣著相機(jī)與錄音案淋,去河邊找鬼座韵。 笑死,一個(gè)胖子當(dāng)著我的面吹牛踢京,可吹牛的內(nèi)容都是我干的誉碴。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼瓣距,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蹈丸?” 一聲冷哼從身側(cè)響起成黄,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎逻杖,沒想到半個(gè)月后奋岁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡荸百,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蓝翰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片畜份。...
    茶點(diǎn)故事閱讀 39,841評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡漂坏,死狀恐怖谷徙,靈堂內(nèi)的尸體忽然破棺而出完慧,到底是詐尸還是另有隱情屈尼,我是刑警寧澤拴孤,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布鞭执,位于F島的核電站兄纺,受9級特大地震影響估脆,放射性物質(zhì)發(fā)生泄漏座云。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一棺聊、第九天 我趴在偏房一處隱蔽的房頂上張望限佩。 院中可真熱鬧祟同,春花似錦、人聲如沸晕城。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽豌熄。三九已至锣险,卻和暖如春芯肤,著一層夾襖步出監(jiān)牢的瞬間崖咨,已是汗流浹背掩幢。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留芍阎,地道東北人缨恒。 一個(gè)月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓岭佳,卻偏偏與公主長得像珊随,于是被迫代替她去往敵國和親叶洞。 傳聞我的和親對象是個(gè)殘疾皇子禀崖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容