日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷(xiāo)解決方案
全面分析前端的網(wǎng)絡(luò)請(qǐng)求方式

 一、前端進(jìn)行網(wǎng)絡(luò)請(qǐng)求的關(guān)注點(diǎn)

潮州網(wǎng)站建設(shè)公司成都創(chuàng)新互聯(lián)公司,潮州網(wǎng)站設(shè)計(jì)制作,有大型網(wǎng)站制作公司豐富經(jīng)驗(yàn)。已為潮州近千家提供企業(yè)網(wǎng)站建設(shè)服務(wù)。企業(yè)網(wǎng)站搭建\外貿(mào)網(wǎng)站制作要多少錢(qián),請(qǐng)找那個(gè)售后服務(wù)好的潮州做網(wǎng)站的公司定做!

大多數(shù)情況下,在前端發(fā)起一個(gè)網(wǎng)絡(luò)請(qǐng)求我們只需關(guān)注下面幾點(diǎn):

  • 傳入基本參數(shù)(url,請(qǐng)求方式)
  • 請(qǐng)求參數(shù)、請(qǐng)求參數(shù)類(lèi)型
  • 設(shè)置請(qǐng)求頭
  • 獲取響應(yīng)的方式
  • 獲取響應(yīng)頭、響應(yīng)狀態(tài)、響應(yīng)結(jié)果
  • 異常處理
  • 攜帶cookie設(shè)置
  • 跨域請(qǐng)求

二、前端進(jìn)行網(wǎng)絡(luò)請(qǐng)求的方式

  • form表單、ifream、刷新頁(yè)面
  • Ajax - 異步網(wǎng)絡(luò)請(qǐng)求的開(kāi)山鼻祖
  • jQuery - 一個(gè)時(shí)代
  • fetch - Ajax的替代者
  • axios、request等眾多開(kāi)源庫(kù)

三、關(guān)于網(wǎng)絡(luò)請(qǐng)求的疑問(wèn)

  • Ajax的出現(xiàn)解決了什么問(wèn)題
  • 原生Ajax如何使用
  • jQuery的網(wǎng)絡(luò)請(qǐng)求方式
  • fetch的用法以及坑點(diǎn)
  • 如何正確的使用fetch
  • 如何選擇合適的跨域方式

帶著以上這些問(wèn)題、關(guān)注點(diǎn)我們對(duì)幾種網(wǎng)絡(luò)請(qǐng)求進(jìn)行一次全面的分析。

四、Ajax的出現(xiàn)解決了什么問(wèn)題

在Ajax出現(xiàn)之前,web程序是這樣工作的:

這種交互的的缺陷是顯而易見(jiàn)的,任何和服務(wù)器的交互都需要刷新頁(yè)面,用戶體驗(yàn)非常差,Ajax的出現(xiàn)解決了這個(gè)問(wèn)題。Ajax全稱Asynchronous JavaScript + XML(異步JavaScript和XML)

使用Ajax,網(wǎng)頁(yè)應(yīng)用能夠快速地將增量更新呈現(xiàn)在用戶界面上,而不需要重載(刷新)整個(gè)頁(yè)面。

Ajax本身不是一種新技術(shù),而是用來(lái)描述一種使用現(xiàn)有技術(shù)集合實(shí)現(xiàn)的一個(gè)技術(shù)方案,瀏覽器的XMLHttpRequest是實(shí)現(xiàn)Ajax最重要的對(duì)象(IE6以下使用ActiveXObject)。

盡管X在Ajax中代表XML, 但由于JSON的許多優(yōu)勢(shì),比如更加輕量以及作為Javascript的一部分,目前JSON的使用比XML更加普遍。

五、原生Ajax的用法

這里主要分析XMLHttpRequest對(duì)象,下面是它的一段基礎(chǔ)使用:       

 
 
 
 
  1. var xhr = new XMLHttpRequest();  
  2.         xhr.open('post','www.xxx.com',true)  
  3.         // 接收返回值  
  4.         xhr.onreadystatechange = function(){  
  5.             if(xhr.readyState === 4 ){  
  6.                 if(xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){  
  7.                     console.log(xhr.responseText);  
  8.                 }  
  9.             }  
  10.         }  
  11.         // 處理請(qǐng)求參數(shù)  
  12.         postData = {"name1":"value1","name2":"value2"};  
  13.         postData = (function(value){  
  14.         var dataString = "";  
  15.         for(var key in value){  
  16.              dataString += key+"="+value[key]+"&";  
  17.         };  
  18.           return dataString;  
  19.         }(postData));  
  20.         // 設(shè)置請(qǐng)求頭  
  21.         xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");  
  22.         // 異常處理  
  23.         xhr.onerror = function() {  
  24.            console.log('Network request failed')  
  25.         }  
  26.         // 跨域攜帶cookie  
  27.         xhr.withCredentials = true;  
  28.         // 發(fā)出請(qǐng)求  
  29.         xhr.send(postData);  

下面分別對(duì)XMLHttpRequest對(duì)象常用的的函數(shù)、屬性、事件進(jìn)行分析。

函數(shù)

open

用于初始化一個(gè)請(qǐng)求,用法: 

 
 
 
 
  1. xhr.open(method, url, async); 
  • method:請(qǐng)求方式,如get、post
  • url:請(qǐng)求的url
  • async:是否為異步請(qǐng)求

send

用于發(fā)送 HTTP 請(qǐng)求,即調(diào)用該方法后HTTP請(qǐng)求才會(huì)被真正發(fā)出,用法: 

 
 
 
 
  1. xhr.send(param) 
  • param:http請(qǐng)求的參數(shù),可以為string、Blob等類(lèi)型。

abort

用于終止一個(gè)ajax請(qǐng)求,調(diào)用此方法后readyState將被設(shè)置為0,用法: 

 
 
 
 
  1. xhr.abort() 

setRequestHeader

用于設(shè)置HTTP請(qǐng)求頭,此方法必須在 open() 方法和 send() 之間調(diào)用,用法: 

 
 
 
 
  1. xhr.setRequestHeader(header, value); 

getResponseHeader

用于獲取http返回頭,如果在返回頭中有多個(gè)一樣的名稱,那么返回的值就會(huì)是用逗號(hào)和空格將值分隔的字符串,用法:

var header = xhr.getResponseHeader(name);

屬性

readyState

用來(lái)標(biāo)識(shí)當(dāng)前XMLHttpRequest對(duì)象所處的狀態(tài),XMLHttpRequest對(duì)象總是位于下列狀態(tài)中的一個(gè):

狀態(tài) 描述
0UNSENT代理被創(chuàng)建,但尚未調(diào)用 open() 方法。
1OPENEDopen() 方法已經(jīng)被調(diào)用。
2HEADERS_RECEIVEDsend() 方法已經(jīng)被調(diào)用,并且頭部和狀態(tài)已經(jīng)可獲得。
3LOADING下載中; responseText 屬性已經(jīng)包含部分?jǐn)?shù)據(jù)。
4DONE下載操作已完成。

status

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

responseType

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

描述
""將 responseType 設(shè)為空字符串與設(shè)置為"text"相同, 是默認(rèn)類(lèi)型 (實(shí)際上是 DOMString)。
"arraybuffer"response 是一個(gè)包含二進(jìn)制數(shù)據(jù)的 JavaScript ArrayBuffer 。
"blob"response 是一個(gè)包含二進(jìn)制數(shù)據(jù)的 Blob 對(duì)象 。
"document"response 是一個(gè) HTML Document  XML XMLDocument ,這取決于接收到的數(shù)據(jù)的 MIME 類(lèi)型。
"json"response 是一個(gè) JavaScript 對(duì)象。這個(gè)對(duì)象是通過(guò)將接收到的數(shù)據(jù)類(lèi)型視為 JSON 解析得到的。
"text"response 是包含在 DOMString 對(duì)象中的文本。

response

返回響應(yīng)的正文,返回的類(lèi)型由上面的responseType決定。

withCredentials

ajax請(qǐng)求默認(rèn)會(huì)攜帶同源請(qǐng)求的cookie,而跨域請(qǐng)求則不會(huì)攜帶cookie,設(shè)置xhr的withCredentials的屬性為true將允許攜帶跨域cookie。

事件回調(diào)

onreadystatechange 

 
 
 
 
  1. xhr.onreadystatechange = callback; 

當(dāng)readyState 屬性發(fā)生變化時(shí),callback會(huì)被觸發(fā)。

onloadstart 

 
 
 
 
  1. xhr.onloadstart = callback; 

在ajax請(qǐng)求發(fā)送之前(readyState==1后, readyState==2前),callback會(huì)被觸發(fā)。

onprogress 

 
 
 
 
  1. xhr.onprogress = function(event){  
  2.   console.log(event.loaded / event.total);  
  3. }  

回調(diào)函數(shù)可以獲取資源總大小total,已經(jīng)加載的資源大小loaded,用這兩個(gè)值可以計(jì)算加載進(jìn)度。

onload 

 
 
 
 
  1. xhr.onload = callback; 

當(dāng)一個(gè)資源及其依賴資源已完成加載時(shí),將觸發(fā)callback,通常我們會(huì)在onload事件中處理返回值。

異常處理

onerror 

 
 
 
 
  1. xhr.onerror = callback; 

當(dāng)ajax資源加載失敗時(shí)會(huì)觸發(fā)callback。

ontimeout 

 
 
 
 
  1. xhr.ontimeout = callback; 

當(dāng)進(jìn)度由于預(yù)定時(shí)間到期而終止時(shí),會(huì)觸發(fā)callback,超時(shí)時(shí)間可使用timeout屬性進(jìn)行設(shè)置。

六、jQuery對(duì)Ajax的封裝

在很長(zhǎng)一段時(shí)間里,人們使用jQuery提供的ajax封裝進(jìn)行網(wǎng)絡(luò)請(qǐng)求,包括$.ajax、$.get、$.post等,這幾個(gè)方法放到現(xiàn)在,我依然覺(jué)得很實(shí)用。 

 
 
 
 
  1. $.ajax({  
  2.     dataType: 'json', // 設(shè)置返回值類(lèi)型  
  3.     contentType: 'application/json', // 設(shè)置參數(shù)類(lèi)型  
  4.     headers: {'Content-Type','application/json'},// 設(shè)置請(qǐng)求頭  
  5.     xhrFields: { withCredentials: true }, // 跨域攜帶cookie  
  6.     data: JSON.stringify({a: [{b:1, a:1}]}), // 傳遞參數(shù)  
  7.     error:function(xhr,status){  // 錯(cuò)誤處理  
  8.        console.log(xhr,status);  
  9.     },  
  10.     success: function (data,status) {  // 獲取結(jié)果  
  11.        console.log(data,status);  
  12.     }  
  13. })  

$.ajax只接收一個(gè)參數(shù),這個(gè)參數(shù)接收一系列配置,其自己封裝了一個(gè)jqXHR對(duì)象,有興趣可以閱讀一下jQuary-ajax 源碼

常用配置:

url

當(dāng)前頁(yè)地址。發(fā)送請(qǐng)求的地址。

type

類(lèi)型:String 請(qǐng)求方式 ("POST" 或 "GET"), 默認(rèn)為 "GET"。注意:其它 HTTP 請(qǐng)求方法,如 PUT 和 DELETE 也可以使用,但僅部分瀏覽器支持。

timeout

類(lèi)型:Number 設(shè)置請(qǐng)求超時(shí)時(shí)間(毫秒)。此設(shè)置將覆蓋全局設(shè)置。

success

類(lèi)型:Function 請(qǐng)求成功后的回調(diào)函數(shù)。

jsonp

在一個(gè)jsonp請(qǐng)求中重寫(xiě)回調(diào)函數(shù)的名字。這個(gè)值用來(lái)替代在"callback=?"這種GET或POST請(qǐng)求中URL參數(shù)里的"callback"部分。

error 類(lèi)型:Function 。請(qǐng)求失敗時(shí)調(diào)用此函數(shù)。

注意:源碼里對(duì)錯(cuò)誤的判定: 

 
 
 
 
  1. isSuccess = status >= 200 && status < 300 || status === 304; 

返回值除了這幾個(gè)狀態(tài)碼都會(huì)進(jìn)error回調(diào)。

dataType 

 
 
 
 
  1. "xml": 返回 XML 文檔,可用 jQuery 處理。  
  2. "html": 返回純文本 HTML 信息;包含的 script 標(biāo)簽會(huì)在插入 dom 時(shí)執(zhí)行。  
  3. "script": 返回純文本 JavaScript 代碼。不會(huì)自動(dòng)緩存結(jié)果。除非設(shè)置了 "cache" 參數(shù)。注意:在遠(yuǎn)程請(qǐng)求時(shí)(不在同一個(gè)域下),所有 POST 請(qǐng)求都將轉(zhuǎn)為 GET 請(qǐng)求。(因?yàn)閷⑹褂?nbsp;DOM 的 script標(biāo)簽來(lái)加載) 
  4.  "json": 返回 JSON 數(shù)據(jù) 。  
  5. "jsonp": JSONP 格式。使用 JSONP 形式調(diào)用函數(shù)時(shí),如 "myurl?callback=?" jQuery 將自動(dòng)替換 ? 為正確的函數(shù)名,以執(zhí)行回調(diào)函數(shù)。  
  6. "text": 返回純文本字符串  

data

類(lèi)型:String 使用JSON.stringify轉(zhuǎn)碼

complete

類(lèi)型:Function 請(qǐng)求完成后回調(diào)函數(shù) (請(qǐng)求成功或失敗之后均調(diào)用)。

async

類(lèi)型:Boolean 默認(rèn)值: true。默認(rèn)設(shè)置下,所有請(qǐng)求均為異步請(qǐng)求。如果需要發(fā)送同步請(qǐng)求,請(qǐng)將此選項(xiàng)設(shè)置為 false。

contentType

類(lèi)型:String 默認(rèn)值: "application/x-www-form-urlencoded"。發(fā)送信息至服務(wù)器時(shí)內(nèi)容編碼類(lèi)型。

鍵值對(duì)這樣組織在一般的情況下是沒(méi)有什么問(wèn)題的,這里說(shuō)的一般是,不帶嵌套類(lèi)型JSON,也就是 簡(jiǎn)單的JSON,形如這樣: 

 
 
 
 
  1. {  
  2.     a: 1,  
  3.     b: 2,  
  4.     c: 3  
  5. }  

但是在一些復(fù)雜的情況下就有問(wèn)題了。 例如在 Ajax 中你要傳一個(gè)復(fù)雜的 json 對(duì)像,也就說(shuō)是對(duì)象嵌數(shù)組,數(shù)組中包括對(duì)象,你這樣傳: application/x-www-form-urlencoded 這種形式是沒(méi)有辦法將復(fù)雜的 JSON 組織成鍵值對(duì)形式。 

 
 
 
 
  1. {  
  2.   data: {  
  3.     a: [{  
  4.       x: 2  
  5.     }]  
  6.   }  
  7. }  

可以用如下方式傳遞復(fù)雜的json對(duì)象 

 
 
 
 
  1. $.ajax({  
  2.     dataType: 'json',  
  3.     contentType: 'application/json',  
  4.     data: JSON.stringify({a: [{b:1, a:1}]})  
  5. })  

七、jQuery的替代者

近年來(lái)前端MV*的發(fā)展壯大,人們?cè)絹?lái)越少的使用jQuery,我們不可能單獨(dú)為了使用jQuery的Ajax api來(lái)單獨(dú)引入他,無(wú)可避免的,我們需要尋找新的技術(shù)方案。

尤雨溪在他的文檔中推薦大家用axios進(jìn)行網(wǎng)絡(luò)請(qǐng)求。axios基于Promise對(duì)原生的XHR進(jìn)行了非常全面的封裝,使用方式也非常的優(yōu)雅。另外,axios同樣提供了在node環(huán)境下的支持,可謂是網(wǎng)絡(luò)請(qǐng)求的重要方案。 

未來(lái)必定還會(huì)出現(xiàn)更優(yōu)秀的封裝,他們有非常周全的考慮以及詳細(xì)的文檔,這里我們不多做考究,我們把關(guān)注的重點(diǎn)放在更底層的APIfetch。

Fetch API 是一個(gè)用用于訪問(wèn)和操縱HTTP管道的強(qiáng)大的原生 API。

這種功能以前是使用 XMLHttpRequest實(shí)現(xiàn)的。Fetch提供了一個(gè)更好的替代方法,可以很容易地被其他技術(shù)使用,例如 Service Workers。Fetch還提供了單個(gè)邏輯位置來(lái)定義其他HTTP相關(guān)概念,例如CORS和HTTP的擴(kuò)展。

可見(jiàn)fetch是作為XMLHttpRequest的替代品出現(xiàn)的。

使用fetch,你不需要再額外加載一個(gè)外部資源。但它還沒(méi)有被瀏覽器完全支持,所以你仍然需要一個(gè) polyfill。

八、fetch的使用

一個(gè)基本的 fetch請(qǐng)求: 

 
 
 
 
  1. const options = {  
  2.     method: "POST", // 請(qǐng)求參數(shù)  
  3.     headers: { "Content-Type": "application/json"}, // 設(shè)置請(qǐng)求頭  
  4.     body: JSON.stringify({name:'123'}), // 請(qǐng)求參數(shù)  
  5.     credentials: "same-origin", // cookie設(shè)置  
  6.     mode: "cors", // 跨域  
  7. }  
  8. fetch('http://www.xxx.com',options)  
  9.   .then(function(response) {  
  10.     return response.json();  
  11.   })  
  12.   .then(function(myJson) {  
  13.     console.log(myJson); // 響應(yīng)數(shù)據(jù)  
  14.   })  
  15.   .catch(function(err){  
  16.     console.log(err); // 異常處理  
  17.   })  

Fetch API提供了一個(gè)全局的fetch()方法,以及幾個(gè)輔助對(duì)象來(lái)發(fā)起一個(gè)網(wǎng)絡(luò)請(qǐng)求。

  • fetch()

fetch()方法用于發(fā)起獲取資源的請(qǐng)求。它返回一個(gè) promise,這個(gè) promise 會(huì)在請(qǐng)求響應(yīng)后被 resolve,并傳回 Response 對(duì)象。

  • Headers

可以通過(guò) Headers() 構(gòu)造函數(shù)來(lái)創(chuàng)建一個(gè)你自己的 headers 對(duì)象,相當(dāng)于 response/request 的頭信息,可以使你查詢到這些頭信息,或者針對(duì)不同的結(jié)果做不同的操作。 

 
 
 
 
  1. var myHeaders = new Headers(); 
  2.  
  3. myHeaders.append("Content-Type", "text/plain");  
  • Request

通過(guò) Request() 構(gòu)造函數(shù)可以創(chuàng)建一個(gè)Request 對(duì)象,這個(gè)對(duì)象可以作為fetch函數(shù)的第二個(gè)參數(shù)。

  • Response

在fetch()處理完promises之后返回一個(gè)Response 實(shí)例,也可以手動(dòng)創(chuàng)建一個(gè)Response實(shí)例。

九、fetch polyfill源碼分析

由于fetch是一個(gè)非常底層的API,所以我們無(wú)法進(jìn)一步的探究它的底層,但是我們可以借助它的polyfill探究它的基本原理,并找出其中的坑點(diǎn)。

代碼結(jié)構(gòu)

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

fetch 封裝

代碼非常清晰:

  • 構(gòu)造一個(gè)Promise對(duì)象并返回
  • 創(chuàng)建一個(gè)Request對(duì)象
  • 創(chuàng)建一個(gè)XMLHttpRequest對(duì)象
  • 取出Request對(duì)象中的請(qǐng)求url,請(qǐng)求方發(fā),open一個(gè)xhr請(qǐng)求,并將Request對(duì)象中存儲(chǔ)的headers取出賦給xhr
  • xhr onload后取出response的status、headers、body封裝Response對(duì)象,調(diào)用resolve。

異常處理

可以發(fā)現(xiàn),調(diào)用reject有三種可能:

  • 1.請(qǐng)求超時(shí)
  • 2.請(qǐng)求失敗

注意:當(dāng)和服務(wù)器建立簡(jiǎn)介,并收到服務(wù)器的異常狀態(tài)碼如404、500等并不能觸發(fā)onerror。當(dāng)網(wǎng)絡(luò)故障時(shí)或請(qǐng)求被阻止時(shí),才會(huì)標(biāo)記為 reject,如跨域、url不存在,網(wǎng)絡(luò)異常等會(huì)觸發(fā)onerror。

所以使用fetch當(dāng)接收到異常狀態(tài)碼都是會(huì)進(jìn)入then而不是catch。這些錯(cuò)誤請(qǐng)求往往要手動(dòng)處理。

  • 3.手動(dòng)終止

可以在request參數(shù)中傳入signal對(duì)象,并對(duì)signal對(duì)象添加abort事件監(jiān)聽(tīng),當(dāng)xhr.readyState變?yōu)?(響應(yīng)內(nèi)容解析完成)后將signal對(duì)象的abort事件監(jiān)聽(tīng)移除掉。

這表示,在一個(gè)fetch請(qǐng)求結(jié)束之前可以調(diào)用signal.abort將其終止。在瀏覽器中可以使用AbortController()構(gòu)造函數(shù)創(chuàng)建一個(gè)控制器,然后使用AbortController.signal屬性

這是一個(gè)實(shí)驗(yàn)中的功能,此功能某些瀏覽器尚在開(kāi)發(fā)中

Headers封裝

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

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

可見(jiàn)header的遍歷即其內(nèi)部map的遍歷。

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

Request對(duì)象

Request對(duì)象接收的兩個(gè)參數(shù)即fetch函數(shù)接收的兩個(gè)參數(shù),一個(gè)參數(shù)可以直接傳遞url,也可以傳遞一個(gè)構(gòu)造好的request對(duì)象;另一個(gè)參數(shù)即控制不同配置的option對(duì)象。

可以傳入credentials、headers、method、mode、signal、referrer等屬性。

這里注意:

  • 傳入的headers被當(dāng)作Headers構(gòu)造函數(shù)的參數(shù)來(lái)構(gòu)造header對(duì)象。

cookie處理

fetch函數(shù)中還有如下的代碼: 

 
 
 
 
  1. if (request.credentials === 'include') {  
  2.     xhr.withCredentials = true  
  3.   } else if (request.credentials === 'omit') {  
  4.     xhr.withCredentials = false  
  5.   }  

默認(rèn)的credentials類(lèi)型為same-origin,即可攜帶同源請(qǐng)求的coodkie。

然后我發(fā)現(xiàn)這里polyfill的實(shí)現(xiàn)和MDN-使用Fetch以及很多資料是不一致的:

mdn: 默認(rèn)情況下,fetch 不會(huì)從服務(wù)端發(fā)送或接收任何 cookies

于是我分別實(shí)驗(yàn)了下使用polyfill和使用原生fetch攜帶cookie的情況,發(fā)現(xiàn)在不設(shè)置credentials的情況下居然都是默認(rèn)攜帶同源cookie的,這和文檔的說(shuō)明說(shuō)不一致的,查閱了許多資料后都是說(shuō)fetch默認(rèn)不會(huì)攜帶cookie,下面是使用原生fetch在瀏覽器進(jìn)行請(qǐng)求的情況:

然后我發(fā)現(xiàn)在MDN-Fetch-Request已經(jīng)指出新版瀏覽器credentials默認(rèn)值已更改為same-origin,舊版依然是omit。

確實(shí)MDN-使用Fetch這里的文檔更新的有些不及時(shí),誤人子弟了...

Response對(duì)象

Response對(duì)象是fetch調(diào)用成功后的返回值:

回顧下fetch中對(duì)Response`的操作:   

 
 
 
 
  1. xhr.onload = function () {  
  2.       var options = {  
  3.         status: xhr.status,  
  4.         statusText: xhr.statusText,  
  5.         headers: parseHeaders(xhr.getAllResponseHeaders() || '')  
  6.       }  
  7.       options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL')  
  8.       var body = 'response' in xhr ? xhr.response : xhr.responseText  
  9.       resolve(new Response(body, options))  
  10.     }  

Response構(gòu)造函數(shù):

可見(jiàn)在構(gòu)造函數(shù)中主要對(duì)options中的status、statusText、headers、url等分別做了處理并掛載到Response對(duì)象上。

構(gòu)造函數(shù)里面并沒(méi)有對(duì)responseText的明確處理,交給了_initBody函數(shù)處理,而Response并沒(méi)有主動(dòng)聲明_initBody屬性,代碼使用Response調(diào)用了Body函數(shù),實(shí)際上_initBody函數(shù)是通過(guò)Body函數(shù)掛載到Response身上的,先來(lái)看看_initBody函數(shù):

可見(jiàn),_initBody函數(shù)根據(jù)xhr.response的類(lèi)型(Blob、FormData、String...),為不同的參數(shù)進(jìn)行賦值,這些參數(shù)在Body方法中得到不同的應(yīng)用,下面具體看看Body函數(shù)還做了哪些其他的操作:

Body函數(shù)中還為Response對(duì)象掛載了四個(gè)函數(shù),text、json、blob、formData,這些函數(shù)中的操作就是將_initBody中得到的不同類(lèi)型的返回值返回。

這也說(shuō)明了,在fetch執(zhí)行完畢后,不能直接在response中獲取到返回值而必須調(diào)用text()、json()等函數(shù)才能獲取到返回值。

這里還有一點(diǎn)需要說(shuō)明:幾個(gè)函數(shù)中都有類(lèi)似下面的邏輯:   

 
 
 
 
  1. var rejected = consumed(this)  
  2.    if (rejected) {  
  3.      return rejected  
  4.    }  

consumed函數(shù): 

 
 
 
 
  1. function consumed(body) {  
  2.   if (body.bodyUsed) {  
  3.     return Promise.reject(new TypeError('Already read'))  
  4.   }  
  5.   body.bodyUsed = true  
  6. }  

每次調(diào)用text()、json()等函數(shù)后會(huì)將bodyUsed變量變?yōu)閠rue,用來(lái)標(biāo)識(shí)返回值已經(jīng)讀取過(guò)了,下一次再讀取直接拋出TypeError('Already read')。這也遵循了原生fetch的原則:

因?yàn)镽esponses對(duì)象被設(shè)置為了 stream 的方式,所以它們只能被讀取一次

十、fetch的坑點(diǎn)

VUE的文檔中對(duì)fetch有下面的描述:

使用fetch還有很多別的注意事項(xiàng),這也是為什么大家現(xiàn)階段還是更喜歡 axios 多一些。當(dāng)然這個(gè)事情在未來(lái)可能會(huì)發(fā)生改變。

由于fetch是一個(gè)非常底層的API,它并沒(méi)有被進(jìn)行很多封裝,還有許多問(wèn)題需要處理:

  • 不能直接傳遞JavaScript對(duì)象作為參數(shù)
  • 需要自己判斷返回值類(lèi)型,并執(zhí)行響應(yīng)獲取返回值的方法
  • 獲取返回值方法只能調(diào)用一次,不能多次調(diào)用
  • 無(wú)法正常的捕獲異常
  • 老版瀏覽器不會(huì)默認(rèn)攜帶cookie
  • 不支持jsonp

十一、對(duì)fetch的封裝

請(qǐng)求參數(shù)處理

支持傳入不同的參數(shù)類(lèi)型: 

 
 
 
 
  1. function stringify(url, data) {  
  2.   var dataString = url.indexOf('?') == -1 ? '?' : '&';  
  3.   for (var key in data) {  
  4.     dataString += key + '=' + data[key] + '&';  
  5.   };  
  6.   return dataString;  
  7. }  
  8. if (request.formData) {  
  9.   requestrequest.body = request.data;  
  10. } else if (/^get$/i.test(request.method)) {  
  11.   request.url = `${request.url}${stringify(request.url, request.data)}`;  
  12. } else if (request.form) {  
  13.   request.headers.set('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8');  
  14.   request.body = stringify(request.data);  
  15. } else {  
  16.   request.headers.set('Content-Type', 'application/json;charset=UTF-8');  
  17.   request.body = JSON.stringify(request.data);  
  18. }  

cookie攜帶

fetch在新版瀏覽器已經(jīng)開(kāi)始默認(rèn)攜帶同源cookie,但在老版瀏覽器中不會(huì)默認(rèn)攜帶,我們需要對(duì)他進(jìn)行統(tǒng)一設(shè)置: 

 
 
 
 
  1. request.credentials =  'same-origin'; // 同源攜帶  
  2.   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í)或請(qǐng)求被阻止時(shí),才會(huì)標(biāo)記為 reject。

因此我們要對(duì)fetch的異常進(jìn)行統(tǒng)一處理 

 
 
 
 
  1. .then(response => {  
  2.   if (response.ok) {  
  3.     return Promise.resolve(response);  
  4.   }else{  
  5.     const error = new Error(`請(qǐng)求失敗! 狀態(tài)碼: ${response.status}, 失敗信息: ${response.statusText}`);  
  6.     error.response = response;  
  7.     return Promise.reject(error);  
  8.   }  
  9. });  

返回值處理

對(duì)不同的返回值類(lèi)型調(diào)用不同的函數(shù)接收,這里必須提前判斷好類(lèi)型,不能多次調(diào)用獲取返回值的方法:.

 
 
 
 
  1. then(response => {  
  2.   let contentType = response.headers.get('content-type');  
  3.   if (contentType.includes('application/json')) {  
  4.     return response.json();  
  5.   } else {  
  6.     return response.text();  
  7.   }  
  8. });  

jsonp

fetch本身沒(méi)有提供對(duì)jsonp的支持,jsonp本身也不屬于一種非常好的解決跨域的方式,推薦使用cors或者nginx解決跨域,具體請(qǐng)看下面的章節(jié)。

fetch封裝好了,可以愉快的使用了。

嗯,axios真好用...

十二、跨域總結(jié)

談到網(wǎng)絡(luò)請(qǐng)求,就不得不提跨域。

瀏覽器的同源策略限制了從同一個(gè)源加載的文檔或腳本如何與來(lái)自另一個(gè)源的資源進(jìn)行交互。這是一個(gè)用于隔離潛在惡意文件的重要安全機(jī)制。通常不允許不同源間的讀操作。

跨域條件:協(xié)議,域名,端口,有一個(gè)不同就算跨域。

下面是解決跨域的幾種方式:

nginx

使用nginx反向代理實(shí)現(xiàn)跨域,參考我這篇文章:前端開(kāi)發(fā)者必備的nginx知識(shí)

cors

CORS是一個(gè)W3C標(biāo)準(zhǔn),全稱是"跨域資源共享"(Cross-origin resource sharing)。它允許瀏覽器向跨源服務(wù)器,發(fā)出XMLHttpRequest請(qǐng)求。

服務(wù)端設(shè)置 Access-Control-Allow-Origin 就可以開(kāi)啟 CORS。 該屬性表示哪些域名可以訪問(wèn)資源,如果設(shè)置通配符則表示所有網(wǎng)站都可以訪問(wèn)資源。  

 
 
 
 
  1. app.all('*', function (req, res, next) {  
  2.     res.header("Access-Control-Allow-Origin", "*");  
  3.     res.header("Access-Control-Allow-Headers", "X-Requested-With");  
  4.     res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");  
  5.     next();  
  6. });  

jsonp

script標(biāo)簽的src屬性中的鏈接可以訪問(wèn)跨域的js腳本,利用這個(gè)特點(diǎn),服務(wù)端不再返回JSON格式的數(shù)據(jù),而是返回一段調(diào)用某個(gè)函數(shù)的js代碼,在src中進(jìn)行了調(diào)用,這樣實(shí)現(xiàn)了跨域。

jquery對(duì)jsonp的支持:     

 
 
 
 
  1. $.ajax({  
  2.          type : "get",  
  3.          url : "http://xxxx"  
  4.          dataType: "jsonp",  
  5.          jsonp:"callback",   
  6.          jsonpCallback: "doo",  
  7.          success : function(data) {  
  8.              console.log(data);  
  9.          }  
  10.      });  

fetch、axios等并沒(méi)有直接提供對(duì)jsonp的支持,如果需要使用這種方式,我們可以嘗試進(jìn)行手動(dòng)封裝:  

 
 
 
 
  1. (function (window,document) {  
  2.     "use strict";  
  3.     var jsonp = function (url,data,callback) {  
  4.         // 1.將傳入的data數(shù)據(jù)轉(zhuǎn)化為url字符串形式  
  5.         // {id:1,name:'jack'} => id=1&name=jack  
  6.         var dataString = url.indexof('?') == -1? '?': '&';  
  7.         for(var key in data){  
  8.             dataString += key + '=' + data[key] + '&';  
  9.         };  
  10.         // 2 處理url中的回調(diào)函數(shù)  
  11.         // cbFuncName回調(diào)函數(shù)的名字 :my_json_cb_名字的前綴 + 隨機(jī)數(shù)(把小數(shù)點(diǎn)去掉)  
  12.         var cbFuncName = 'my_json_cb_' + Math.random().toString().replace('.','');  
  13.         dataString += 'callback=' + cbFuncName;  
  14.         // 3.創(chuàng)建一個(gè)script標(biāo)簽并插入到頁(yè)面中  
  15.         var scriptEle = document.createElement('script');  
  16.         scriptEle.src = url + dataString;  
  17.         // 4.掛載回調(diào)函數(shù)  
  18.         window[cbFuncName] = function (data) {  
  19.             callback(data);  
  20.             // 處理完回調(diào)函數(shù)的數(shù)據(jù)之后,刪除jsonp的script標(biāo)簽  
  21.             document.body.removeChild(scriptEle);  
  22.         }  
  23.         document.body.appendChild(scriptEle);  
  24.     }  
  25.     window.$jsonpjsonp = jsonp;  
  26. })(window,document)  

postMessage跨域

postMessage()方法允許來(lái)自不同源的腳本采用異步方式進(jìn)行有限的通信,可以實(shí)現(xiàn)跨文本檔、多窗口、跨域消息傳遞。 

 
 
 
 
  1. //捕獲iframe  
  2. var domain = 'http://scriptandstyle.com';  
  3. var iframe = document.getElementById('myIFrame').contentWindow;  
  4. //發(fā)送消息  
  5. setInterval(function(){  
  6.     var message = 'Hello!  The time is: ' + (new Date().getTime());  
  7.     console.log('blog.local:  sending message:  ' + message);  
  8.         //send the message and target URI  
  9.     iframe.postMessage(message,domain);   
  10. },6000);  
 
 
 
 
  1. //響應(yīng)事件  
  2. window.addEventListener('message',function(event) {  
  3.     if(event.origin !== 'http://davidwalsh.name') return;  
  4.     console.log('message received:  ' + event.data,event);  
  5.     event.source.postMessage('holla back youngin!',event.origin);  
  6. },false);  

postMessage跨域適用于以下場(chǎng)景:同瀏覽器多窗口間跨域通信、iframe間跨域通信。

WebSocket

WebSocket 是一種雙向通信協(xié)議,在建立連接之后,WebSocket 的 server 與 client 都能主動(dòng)向?qū)Ψ桨l(fā)送或接收數(shù)據(jù)而不受同源策略的限制。         

 
 
 
 
  1. function WebSocketTest(){  
  2.             if ("WebSocket" in window){  
  3.                alert("您的瀏覽器支持 WebSocket!");  
  4.                // 打開(kāi)一個(gè) web socket  
  5.                var ws = new WebSocket("ws://localhost:3000/abcd");  
  6.                ws.onopen = function(){  
  7.                   // Web Socket 已連接上,使用 send() 方法發(fā)送數(shù)據(jù)  
  8.                   ws.send("發(fā)送數(shù)據(jù)");  
  9.                   alert("數(shù)據(jù)發(fā)送中...");  
  10.                };  
  11.                ws.onmessage = function (evt) {   
  12.                   var received_msg = evt.data;  
  13.                   alert("數(shù)據(jù)已接收...");  
  14.                };  
  15.                ws.onclose = function(){   
  16.                   // 關(guān)閉 websocket  
  17.                   alert("連接已關(guān)閉...");   
  18.                };  
  19.             } else{  
  20.                // 瀏覽器不支持 WebSocket  
  21.                alert("您的瀏覽器不支持 WebSocket!");  
  22.             }  
  23.          }  

文中如有錯(cuò)誤,歡迎在評(píng)論區(qū)指正,謝謝閱讀。


本文標(biāo)題:全面分析前端的網(wǎng)絡(luò)請(qǐng)求方式
當(dāng)前路徑:http://www.5511xx.com/article/djidpei.html