?
このドキュメントでは、 php中國語ネットマニュアル リリース
Ajax指的是不刷新頁面,發(fā)出異步請求,向服務(wù)器端要求數(shù)據(jù),然后再進行處理的方法。
XMLHttpRequest對象
Open()
setRequestHeader()
send()
readyState屬性和readyStateChange事件
progress事件
服務(wù)器返回的信息
setRequestHeader方法
overrideMimeType方法
responseType屬性
文件上傳
JSONP
CORS
Fetch API
基本用法
fetch()
Headers
Request對象
Response
body屬性
參考鏈接
XMLHttpRequest對象用于從JavaScript發(fā)出HTTP請求,下面是典型用法。
// 新建一個XMLHttpRequest實例對象 var xhr = new XMLHttpRequest(); // 指定通信過程中狀態(tài)改變時的回調(diào)函數(shù) xhr.onreadystatechange = function(){ // 通信成功時,狀態(tài)值為4 var completed = 4; if(xhr.readyState === completed){ if(xhr.status === 200){ // 處理服務(wù)器發(fā)送過來的數(shù)據(jù) }else{ // 處理錯誤 } } }; // open方式用于指定HTTP動詞、請求的網(wǎng)址、是否異步 xhr.open('GET', '/endpoint', true); // 發(fā)送HTTP請求 xhr.send(null);
open方法用于指定發(fā)送HTTP請求的參數(shù),它有三個參數(shù)如下:
發(fā)送方法,一般來說為“GET”、“POST”、“PUT”和“DELETE”中的一個值。
網(wǎng)址。
是否異步,true表示異步,false表示同步。
下面發(fā)送POST請求的例子。
xhr.open('POST', encodeURI('someURL')); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.onload = function() {}; xhr.send(encodeURI('dataString'));
上面方法中,open方法向指定URL發(fā)出POST請求,send方法送出實際的數(shù)據(jù)。
setRequestHeader方法用于設(shè)置HTTP請求的頭信息。
send方法用于實際發(fā)出HTTP請求。如果不帶參數(shù),就表示HTTP請求只包含頭信息,也就是只有一個URL,典型例子就是GET請求;如果帶有參數(shù),就表示除了頭信息,還帶有包含具體數(shù)據(jù)的信息體,典型例子就是POST請求。
在XHR 2之中,send方法可以發(fā)送許多類型的數(shù)據(jù)。
void send(); void send(ArrayBuffer data); void send(Blob data); void send(Document data); void send(DOMString data); void send(FormData data);
Blob類型可以用來發(fā)送二進制數(shù)據(jù),這使得通過Ajax上傳文件成為可能。
FormData類型可以用于構(gòu)造表單數(shù)據(jù)。
var formData = new FormData(); formData.append('username', '張三'); formData.append('email', 'zhangsan@example.com'); formData.append('birthDate', 1940); xhr.send(formData);
上面的代碼構(gòu)造了一個formData對象,然后使用send方法發(fā)送。它的效果與點擊下面表單的submit按鈕是一樣的。
<form id='registration' name='registration' action='/register'> <input type='text' name='username' value='張三'> <input type='email' name='email' value='zhangsan@example.com'> <input type='number' name='birthDate' value='1940'> <input type='submit' onclick='return sendForm(this.form);'> </form>
FormData對象還可以對現(xiàn)有表單添加數(shù)據(jù),這為我們操作表單提供了極大的靈活性。
function sendForm(form) { var formData = new FormData(form); formData.append('csrf', 'e69a18d7db1286040586e6da1950128c'); var xhr = new XMLHttpRequest(); xhr.open('POST', form.action, true); xhr.onload = function(e) { // ... }; xhr.send(formData); return false; } var form = document.querySelector('#registration'); sendForm(form);
FormData對象也能用來模擬File控件,進行文件上傳。
function uploadFiles(url, files) { var formData = new FormData(); for (var i = 0, file; file = files[i]; ++i) { formData.append(file.name, file); } var xhr = new XMLHttpRequest(); xhr.open('POST', url, true); xhr.onload = function(e) { ... }; xhr.send(formData); // multipart/form-data } document.querySelector('input[type="file"]').addEventListener('change', function(e) { uploadFiles('/server', this.files); }, false);
在通信過程中,每當(dāng)發(fā)生狀態(tài)變化的時候,readyState屬性的值就會發(fā)生改變。
這個值每一次變化,都會觸發(fā)readyStateChange事件。我們可以指定這個事件的回調(diào)函數(shù),對不同狀態(tài)進行不同處理。尤其是當(dāng)狀態(tài)變?yōu)?的時候,表示通信成功,這時回調(diào)函數(shù)就可以處理服務(wù)器傳送回來的數(shù)據(jù)。
上傳文件時,XMLHTTPRequest對象的upload屬性有一個progress,會不斷返回上傳的進度。
假定網(wǎng)頁上有一個progress元素。
<progress min="0" max="100" value="0">0% complete</progress>
文件上傳時,對upload屬性指定progress事件回調(diào)函數(shù),即可獲得上傳的進度。
function upload(blobOrFile) { var xhr = new XMLHttpRequest(); xhr.open('POST', '/server', true); xhr.onload = function(e) { ... }; // Listen to the upload progress. var progressBar = document.querySelector('progress'); xhr.upload.onprogress = function(e) { if (e.lengthComputable) { progressBar.value = (e.loaded / e.total) * 100; progressBar.textContent = progressBar.value; // Fallback for unsupported browsers. } }; xhr.send(blobOrFile); } upload(new Blob(['hello world'], {type: 'text/plain'}));
下面是一個上傳ArrayBuffer對象的例子。
function sendArrayBuffer() { var xhr = new XMLHttpRequest(); xhr.open('POST', '/server', true); xhr.onload = function(e) { ... }; var uInt8Array = new Uint8Array([1, 2, 3]); xhr.send(uInt8Array.buffer); }
(1)status屬性
status屬性表示返回的HTTP狀態(tài)碼。一般來說,如果通信成功的話,這個狀態(tài)碼是200。
(2)responseText屬性
responseText屬性表示服務(wù)器返回的文本數(shù)據(jù)。
setRequestHeader方法用于設(shè)置HTTP頭信息。
xhr.setRequestHeader('Content-Type', 'application/json'); xhr.setRequestHeader('Content-Length', JSON.stringify(data).length); xhr.send(JSON.stringify(data));
上面代碼首先設(shè)置頭信息Content-Type,表示發(fā)送JSON格式的數(shù)據(jù);然后設(shè)置Content-Length,表示數(shù)據(jù)長度;最后發(fā)送JSON數(shù)據(jù)。
該方法用來指定服務(wù)器返回數(shù)據(jù)的MIME類型。
傳統(tǒng)上,如果希望從服務(wù)器取回二進制數(shù)據(jù),就要使用這個方法,人為將數(shù)據(jù)類型偽裝成文本數(shù)據(jù)。
var xhr = new XMLHttpRequest(); xhr.open('GET', '/path/to/image.png', true); // 強制將MIME改為文本類型 xhr.overrideMimeType('text/plain; charset=x-user-defined'); xhr.onreadystatechange = function(e) { if (this.readyState == 4 && this.status == 200) { var binStr = this.responseText; for (var i = 0, len = binStr.length; i < len; ++i) { var c = binStr.charCodeAt(i); var byte = c & 0xff; // 去除高位字節(jié),留下低位字節(jié) } } }; xhr.send(); xhr.send();
上面代碼中,因為傳回來的是二進制數(shù)據(jù),首先用xhr.overrideMimeType方法強制改變它的MIME類型,偽裝成文本數(shù)據(jù)。字符集必需指定為“x-user-defined”,如果是其他字符集,瀏覽器內(nèi)部會強制轉(zhuǎn)碼,將其保存成UTF-16的形式。字符集“x-user-defined”其實也會發(fā)生轉(zhuǎn)碼,瀏覽器會在每個字節(jié)前面再加上一個字節(jié)(0xF700-0xF7ff),因此后面要對每個字符進行一次與運算(&),將高位的8個位去除,只留下低位的8個位,由此逐一讀出原文件二進制數(shù)據(jù)的每個字節(jié)。
這種方法很麻煩,在XMLHttpRequest版本升級以后,一般采用下面的指定responseType的方法。
XMLHttpRequest對象有一個responseType屬性,用來指定服務(wù)器返回數(shù)據(jù)(xhr.response)的類型。
XHR 2允許用戶自行設(shè)置這個屬性,也就是指定返回數(shù)據(jù)的類型,可以設(shè)置如下的值:
'text':返回類型為字符串,這是默認值。
'arraybuffer':返回類型為ArrayBuffer。
'blob':返回類型為Blob。
'document':返回類型為Document。
'json':返回類型為JSON object。
text類型適合大多數(shù)情況,而且直接處理文本也比較方便,document類型適合返回XML文檔的情況,blob類型適合讀取二進制數(shù)據(jù),比如圖片文件。
var xhr = new XMLHttpRequest(); xhr.open('GET', '/path/to/image.png', true); xhr.responseType = 'blob'; xhr.onload = function(e) { if (this.status == 200) { var blob = new Blob([this.response], {type: 'image/png'}); // ... } }; xhr.send();
如果將這個屬性設(shè)為ArrayBuffer,就可以按照數(shù)組的方式處理二進制數(shù)據(jù)。
var xhr = new XMLHttpRequest(); xhr.open('GET', '/path/to/image.png', true); xhr.responseType = 'arraybuffer'; xhr.onload = function(e) { var uInt8Array = new Uint8Array(this.response); for (var i = 0, len = binStr.length; i < len; ++i) { // var byte = uInt8Array[i]; } }; xhr.send();
如果將這個屬性設(shè)為“json”,支持JSON的瀏覽器(Firefox>9,chrome>30),就會自動對返回數(shù)據(jù)調(diào)用JSON.parse() 方法。也就是說,你從xhr.response屬性(注意,不是xhr.responseText屬性)得到的不是文本,而是一個JSON對象。
XHR2支持Ajax的返回類型為文檔,即xhr.responseType="document" 。這意味著,對于那些打開CORS的網(wǎng)站,我們可以直接用Ajax抓取網(wǎng)頁,然后不用解析HTML字符串,直接對XHR回應(yīng)進行DOM操作。
通常,我們使用file控件實現(xiàn)文件上傳。
<form id="file-form" action="handler.php" method="POST"> <input type="file" id="file-select" name="photos[]" multiple/> <button type="submit" id="upload-button">上傳</button> </form>
上面HTML代碼中,file控件的multiple屬性,指定可以一次選擇多個文件;如果沒有這個屬性,則一次只能選擇一個文件。
file對象的files屬性,返回一個FileList對象,包含了用戶選中的文件。
var fileSelect = document.getElementById('file-select'); var files = fileSelect.files;
然后,新建一個FormData對象的實例,用來模擬發(fā)送到服務(wù)器的表單數(shù)據(jù),把選中的文件添加到這個對象上面。
var formData = new FormData(); for (var i = 0; i < files.length; i++) { var file = files[i]; if (!file.type.match('image.*')) { continue; } formData.append('photos[]', file, file.name); }
上面代碼中的FormData對象的append方法,除了可以添加文件,還可以添加二進制對象(Blob)或者字符串。
// Files formData.append(name, file, filename); // Blobs formData.append(name, blob, filename); // Strings formData.append(name, value);
append方法的第一個參數(shù)是表單的控件名,第二個參數(shù)是實際的值,第三個參數(shù)是可選的,通常是文件名。
最后,使用Ajax方法向服務(wù)器上傳文件。
var xhr = new XMLHttpRequest(); xhr.open('POST', 'handler.php', true); xhr.onload = function () { if (xhr.status !== 200) { alert('An error occurred!'); } }; xhr.send(formData);
目前,各大瀏覽器(包括IE 10)都支持Ajax上傳文件。
除了使用FormData接口上傳,也可以直接使用File API上傳。
var file = document.getElementById('test-input').files[0]; var xhr = new XMLHttpRequest(); xhr.open('POST', 'myserver/uploads'); xhr.setRequestHeader('Content-Type', file.type); xhr.send(file);
可以看到,上面這種寫法比FormData的寫法,要簡單很多。
JSONP是一種常見做法,用于服務(wù)器與客戶端之間的數(shù)據(jù)傳輸,主要為了規(guī)避瀏覽器的同域限制。因為Ajax只能向當(dāng)前網(wǎng)頁所在的域名發(fā)出HTTP請求(除非使用下文要提到的CORS,但并不是所有服務(wù)器都支持CORS),所以JSONP就采用在網(wǎng)頁中動態(tài)插入script元素的做法,向服務(wù)器請求腳本文件。
function addScriptTag(src){ var script = document.createElement('script'); script.setAttribute("type","text/javascript"); script.src = src; document.body.appendChild(script); } window.onload = function(){ addScriptTag("http://example.com/ip?callback=foo"); } function foo(data) { console.log('Your public IP address is: ' + data.ip); };
上面代碼使用了JSONP,運行以后當(dāng)前網(wǎng)頁就可以直接處理example.com返回的數(shù)據(jù)了。
由于script元素返回的腳本文件,是直接作為代碼運行的,不像Ajax請求返回的是JSON字符串,需要用JSON.parse方法將字符串轉(zhuǎn)為JSON對象。于是,為了方便起見,許多服務(wù)器支持JSONP指定回調(diào)函數(shù)的名稱,直接將JSON數(shù)據(jù)放入回調(diào)函數(shù)的參數(shù),如此一來就省略了將字符串解析為JSON對象的步驟。
請看下面的例子,假定訪問 http://example.com/ip ,返回如下JSON數(shù)據(jù):
{"ip":"8.8.8.8"}
現(xiàn)在服務(wù)器允許客戶端請求時使用callback參數(shù)指定回調(diào)函數(shù)。訪問 http://example.com/ip?callback=foo ,返回的數(shù)據(jù)變成:
foo({"ip":"8.8.8.8"})
這時,如果客戶端定義了foo函數(shù),該函數(shù)就會被立即調(diào)用,而作為參數(shù)的JSON數(shù)據(jù)被視為JavaScript對象,而不是字符串,因此避免了使用JSON.parse的步驟。
function foo(data) { console.log('Your public IP address is: ' + data.ip); };
jQuery的getJSON方法就是JSONP的一個應(yīng)用。
$.getJSON( "http://example.com/api", function (data){ .... })
$.getJSON方法的第一個參數(shù)是服務(wù)器網(wǎng)址,第二個參數(shù)是回調(diào)函數(shù),該回調(diào)函數(shù)的參數(shù)就是服務(wù)器返回的JSON數(shù)據(jù)。
CORS的全稱是“跨域資源共享”(Cross-origin resource sharing),它提出一種方法,允許JavaScript代碼向另一個域名發(fā)出XMLHttpRequests請求,從而克服了傳統(tǒng)上Ajax只能在同一個域名下使用的限制(same origin security policy)。
所有主流瀏覽器都支持該方法,不過IE8和IE9的該方法不是部署在XMLHttpRequest對象,而是部署在XDomainRequest對象。檢查瀏覽器是否支持的代碼如下:
var request = new XMLHttpRequest(); if("withCredentials" in request) { // 發(fā)出跨域請求 }
CORS的原理其實很簡單,就是增加一條HTTP頭信息的查詢,詢問服務(wù)器端,當(dāng)前請求的域名是否在許可名單之中,以及可以使用哪些HTTP動詞。如果得到肯定的答復(fù),就發(fā)出XMLHttpRequest請求。這種機制叫做“預(yù)檢”(preflight)。
“預(yù)檢”的專用HTTP頭信息是Origin。假定用戶正在瀏覽來自www.example.com的網(wǎng)頁,該網(wǎng)頁需要向Google請求數(shù)據(jù),這時瀏覽器會向該域名詢問是否同意跨域請求,發(fā)出的HTTP頭信息如下:
OPTIONS /resources/post-here/ HTTP/1.1 Host: www.google.com User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Connection: keep-alive Origin: http://www.example.com Access-Control-Request-Method: POST Access-Control-Request-Headers: X-PINGOTHER
上面的HTTP請求,它的動詞是OPTIONS,表示這是一個“預(yù)檢”請求。除了提供瀏覽器信息,里面關(guān)鍵的一行是Origin頭信息。
Origin: http://www.example.com
這行HTTP頭信息表示,請求來自www.example.com。服務(wù)端如果同意,就返回一個Access-Control-Allow-Origin頭信息。
預(yù)檢請求中,瀏覽器還告訴服務(wù)器,實際發(fā)出請求,將使用HTTP動詞POST,以及一個自定義的頭信息X-PINGOTHER。
Access-Control-Request-Method: POST Access-Control-Request-Headers: X-PINGOTHER
服務(wù)器收到預(yù)檢請求之后,做出了回應(yīng)。
HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 01:15:39 GMT Server: Apache/2.0.61 (Unix) Access-Control-Allow-Origin: http://www.example.com Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: X-PINGOTHER Access-Control-Max-Age: 1728000 Vary: Accept-Encoding, Origin Content-Encoding: gzip Content-Length: 0 Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Content-Type: text/plain
上面的HTTP回應(yīng)里面,關(guān)鍵的是Access-Control-Allow-Origin頭信息。這表示服務(wù)器同意www.example.com的跨域請求。
Access-Control-Allow-Origin: http://www.example.com
如果不同意,服務(wù)器端會返回一個錯誤。
如果服務(wù)器端對所有網(wǎng)站都開放,可以返回一個星號(*)通配符。
Access-Control-Allow-Origin: *
服務(wù)器還告訴瀏覽器,允許的HTTP動詞是POST、GET、OPTIONS,也允許自定義的頭信息X-PINGOTHER,
Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: X-PINGOTHER Access-Control-Max-Age: 1728000
如果服務(wù)器通過了預(yù)檢請求,則以后每次瀏覽器正常的HTTP請求,都會有一個origin頭信息;服務(wù)器的回應(yīng),也都會有一個Access-Control-Allow-Origin頭信息。Access-Control-Max-Age頭信息表示,允許緩存該條回應(yīng)1728000秒(即20天),在此期間,不用發(fā)出另一條預(yù)檢請求。
由于整個過程都是瀏覽器自動后臺完成,不用用戶參與,所以對于開發(fā)者來說,使用Ajax跨域請求與同域請求沒有區(qū)別,代碼完全一樣。但是,這需要服務(wù)器的支持,所以在使用CORS之前,要查看一下所請求的網(wǎng)站是否支持。
CORS機制默認不發(fā)送cookie和HTTP認證信息,除非在Ajax請求中打開withCredentials屬性。
var request = new XMLHttpRequest(); request.withCredentials = true;
同時,服務(wù)器返回HTTP頭信息時,也必須打開Access-Control-Allow-Credentials選項。否則,瀏覽器會忽略服務(wù)器返回的回應(yīng)。
Access-Control-Allow-Credentials: true
需要注意的是,此時Access-Control-Allow-Origin不能指定為星號,必須指定明確的、與請求網(wǎng)頁一致的域名。同時,cookie依然遵循同源政策,只有用服務(wù)器域名(前例是www.google.com)設(shè)置的cookie才會上傳,其他域名下的cookie并不會上傳,且網(wǎng)頁代碼中的document.cookie也無法讀取www.google.com域名下的cookie。
CORS機制與JSONP模式的使用目的相同,而且更強大。JSONP只支持GET請求,CORS可以支持所有類型的HTTP請求。在發(fā)生錯誤的情況下,CORS可以得到更詳細的錯誤信息,部署更有針對性的錯誤處理代碼。JSONP的優(yōu)勢在于可以用于老式瀏覽器,以及可以向不支持CORS的網(wǎng)站請求數(shù)據(jù)。
Ajax操作所用的XMLHttpRequest對象,已經(jīng)有十多年的歷史,它的API設(shè)計并不是很好,輸入、輸出、狀態(tài)都在同一個接口管理,容易寫出非?;靵y的代碼。Fetch API是一種新規(guī)范,用來取代XMLHttpRequest對象。它主要有兩個特點,一是簡化接口,將API分散在幾個不同的對象上,二是返回Promise對象,避免了嵌套的回調(diào)函數(shù)。
檢查瀏覽器是否部署了這個API的代碼如下。
if (fetch in window){ // 支持 } else { // 不支持 }
下面是一個Fetch API的簡單例子。
var URL = 'http://some/path'; fetch(URL).then(function(response) { return response.json(); }).then(function(json) { someOperator(json); });
上面代碼向服務(wù)器請求JSON文件,獲取后再做進一步處理。
下面比較XMLHttpRequest寫法與Fetch寫法的不同。
function reqListener() { var data = JSON.parse(this.responseText); console.log(data); } function reqError(err) { console.log('Fetch Error :-S', err); } var oReq = new XMLHttpRequest(); oReq.onload = reqListener; oReq.onerror = reqError; oReq.open('get', './api/some.json', true); oReq.send();
同樣的操作用Fetch實現(xiàn)如下。
fetch('./api/some.json') .then(function(response) { if (response.status !== 200) { console.log('請求失敗,狀態(tài)碼:' + response.status); return; } response.json().then(function(data) { console.log(data); }); }).catch(function(err) { console.log('出錯:', err); });
上面代碼中,因為HTTP請求返回的response對象是一個Stream對象,所以需要使用response.json
方法轉(zhuǎn)為JSON格式,不過這個方法返回的是一個Promise對象。
fetch方法的第一個參數(shù)可以是URL字符串,也可以是后文要講到的Request對象實例。Fetch方法返回一個Promise對象,并將一個response對象傳給回調(diào)函數(shù)。
response對象還有一個ok屬性,如果返回的狀態(tài)碼在200到299之間(即請求成功),這個屬性為true,否則為false。因此,上面的代碼可以寫成下面這樣。
fetch("./api/some.json").then(function(response) { if (response.ok) { response.json().then(function(data) { console.log(data); }); } else { console.log("請求失敗,狀態(tài)碼為", response.status); } }, function(err) { console.log("出錯:", err); });
response對象除了json方法,還包含了HTTP回應(yīng)的元數(shù)據(jù)。
fetch('users.json').then(function(response) { console.log(response.headers.get('Content-Type')); console.log(response.headers.get('Date')); console.log(response.status); console.log(response.statusText); console.log(response.type); console.log(response.url); });
上面代碼中,response對象有很多屬性,其中的response.type
屬性比較特別,表示HTTP回應(yīng)的類型,它有以下三個值。
basic:正常的同域請求
cors:CORS機制下的跨域請求
opaque:非CORS機制下的跨域請求,這時無法讀取返回的數(shù)據(jù),也無法判斷是否請求成功
如果需要在CORS機制下發(fā)出跨域請求,需要指明狀態(tài)。
fetch('http://some-site.com/cors-enabled/some.json', {mode: 'cors'}) .then(function(response) { return response.text(); }) .then(function(text) { console.log('Request successful', text); }) .catch(function(error) { log('Request failed', error) });
除了指定模式,fetch方法的第二個參數(shù)還可以用來配置其他值,比如指定cookie連同HTTP請求一起發(fā)出。
fetch(url, { credentials: 'include' })
發(fā)出POST請求的寫法如下。
fetch("http://www.example.org/submit.php", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: "firstName=Nikhil&favColor=blue&password=easytoguess"}).then(function(res) { if (res.ok) { console.log("Perfect! Your settings are saved."); } else if (res.status == 401) { console.log("Oops! You are not authorized."); } }, function(e) { console.log("Error submitting form!"); });
目前,還有一些XMLHttpRequest對象可以做到,但是Fetch API還沒做到的地方,比如中途中斷HTTP請求,以及獲取HTTP請求的進度。這些不足與Fetch返回的是Promise對象有關(guān)。
Fetch API引入三個新的對象(也是構(gòu)造函數(shù)):Headers, Request 和 Response。其中,Headers對象用來構(gòu)造/讀取HTTP數(shù)據(jù)包的頭信息。
var content = "Hello World";var reqHeaders = new Headers(); reqHeaders.append("Content-Type", "text/plain"); reqHeaders.append("Content-Length", content.length.toString()); reqHeaders.append("X-Custom-Header", "ProcessThisImmediately");
Headers對象的實例,除了使用append方法添加屬性,也可以直接通過構(gòu)造函數(shù)一次性生成。
reqHeaders = new Headers({ "Content-Type": "text/plain", "Content-Length": content.length.toString(), "X-Custom-Header": "ProcessThisImmediately", });
Headers對象實例還提供了一些工具方法。
reqHeaders.has("Content-Type") // true reqHeaders.has("Set-Cookie") // false reqHeaders.set("Content-Type", "text/html") reqHeaders.append("X-Custom-Header", "AnotherValue") reqHeaders.get("Content-Length") // 11 reqHeaders.getAll("X-Custom-Header") // ["ProcessThisImmediately", "AnotherValue"] reqHeaders.delete("X-Custom-Header") reqHeaders.getAll("X-Custom-Header") // []
生成Header實例以后,可以將它作為第二個參數(shù),傳入Request方法。
var headers = new Headers(); headers.append('Accept', 'application/json'); var request = new Request(URL, {headers: headers}); fetch(request).then(function(response) { console.log(response.headers); });
同樣地,Headers實例可以用來構(gòu)造Response方法。
var headers = new Headers({ 'Content-Type': 'application/json', 'Cache-Control': 'max-age=3600' }); var response = new Response( JSON.stringify({photos: {photo: []}}), {'status': 200, headers: headers} ); response.json().then(function(json) { insertPhotos(json); });
上面代碼中,構(gòu)造了一個HTTP回應(yīng)。目前,瀏覽器構(gòu)造HTTP回應(yīng)沒有太大用處,但是隨著Service Worker的部署,不久瀏覽器就可以向Service Worker發(fā)出HTTP回應(yīng)。
Request對象用來構(gòu)造HTTP請求。
var req = new Request("/index.html"); req.method // "GET" req.url // "http://example.com/index.html"
Request對象的第二個參數(shù),表示配置對象。
var uploadReq = new Request("/uploadImage", { method: "POST", headers: { "Content-Type": "image/png", }, body: "image data" });
上面代碼指定Request對象使用POST方法發(fā)出,并指定HTTP頭信息和信息體。
下面是另一個例子。
var req = new Request(URL, {method: 'GET', cache: 'reload'}); fetch(req).then(function(response) { return response.json(); }).then(function(json) { someOperator(json); });
上面代碼中,指定請求方法為GET,并且要求瀏覽器不得緩存response。
Request對象實例有兩個屬性是只讀的,不能手動設(shè)置。一個是referrer屬性,表示請求的來源,由瀏覽器設(shè)置,有可能是空字符串。另一個是context屬性,表示請求發(fā)出的上下文,如果是image,表示是從img標(biāo)簽發(fā)出,如果是worker,表示是從worker腳本發(fā)出,如果是fetch,表示是從fetch函數(shù)發(fā)出的。
Request對象實例的mode屬性,用來設(shè)置是否跨域,合法的值有以下三種:same-origin、no-cors(默認值)、cors。當(dāng)設(shè)置為same-origin時,只能向同域的URL發(fā)出請求,否則會報錯。
var arbitraryUrl = document.getElementById("url-input").value; fetch(arbitraryUrl, { mode: "same-origin" }).then(function(res) { console.log("Response succeeded?", res.ok); }, function(e) { console.log("Please enter a same-origin URL!"); });
上面代碼中,如果用戶輸入的URL不是同域的,將會報錯,否則就會發(fā)出請求。
如果mode屬性為no-cors,就與默認的瀏覽器行為沒有不同,類似script標(biāo)簽加載外部腳本文件、img標(biāo)簽加載外部圖片。如果mode屬性為cors,就可以向部署了CORS機制的服務(wù)器,發(fā)出跨域請求。
var u = new URLSearchParams(); u.append('method', 'flickr.interestingness.getList'); u.append('api_key', '<insert api key here>'); u.append('format', 'json'); u.append('nojsoncallback', '1'); var apiCall = fetch('https://api.flickr.com/services/rest?' + u); apiCall.then(function(response) { return response.json().then(function(json) { // photo is a list of photos. return json.photos.photo; }); }).then(function(photos) { photos.forEach(function(photo) { console.log(photo.title); }); });
上面代碼是向Flickr API發(fā)出圖片請求的例子。
Request對象的一個很有用的功能,是在其他Request實例的基礎(chǔ)上,生成新的Request實例。
var postReq = new Request(req, {method: 'POST'});
fetch方法返回Response對象實例,它有以下屬性。
status:整數(shù)值,表示狀態(tài)碼(比如200)
statusText:字符串,表示狀態(tài)信息,默認是“OK”
ok:布爾值,表示狀態(tài)碼是否在200-299的范圍內(nèi)
headers:Headers對象,表示HTTP回應(yīng)的頭信息
url:字符串,表示HTTP請求的網(wǎng)址
type:字符串,合法的值有五個basic、cors、default、error、opaque。basic表示正常的同域請求;cors表示CORS機制的跨域請求;error表示網(wǎng)絡(luò)出錯,無法取得信息,status屬性為0,headers屬性為空,并且導(dǎo)致fetch函數(shù)返回Promise對象被拒絕;opaque表示非CORS機制的跨域請求,受到嚴(yán)格限制。
Response對象還有兩個靜態(tài)方法。
Response.error() 返回一個type屬性為error的Response對象實例
Response.redirect(url, status) 返回的Response對象實例會重定向到另一個URL
Request對象和Response對象都有body屬性,表示請求的內(nèi)容。body屬性可能是以下的數(shù)據(jù)類型。
ArrayBuffer
ArrayBufferView (Uint8Array等)
Blob/File
string
URLSearchParams
FormData
var form = new FormData(document.getElementById('login-form')); fetch("/login", { method: "POST", body: form })
上面代碼中,Request對象的body屬性為表單數(shù)據(jù)。
Request對象和Response對象都提供以下方法,用來讀取body。
arrayBuffer()
blob()
json()
text()
formData()
注意,上面這些方法都只能使用一次,第二次使用就會報錯,也就是說,body屬性只能讀取一次。Request對象和Response對象都有bodyUsed屬性,返回一個布爾值,表示body是否被讀取過。
var res = new Response("one time use"); console.log(res.bodyUsed); // false res.text().then(function(v) { console.log(res.bodyUsed); // true }); console.log(res.bodyUsed); // true res.text().catch(function(e) { console.log("Tried to read already consumed Response"); });
上面代碼中,第二次通過text方法讀取Response對象實例的body時,就會報錯。
這是因為body屬性是一個stream對象,數(shù)據(jù)只能單向傳送一次。這樣的設(shè)計是為了允許JavaScript處理視頻、音頻這樣的大型文件。
如果希望多次使用body屬性,可以使用Response對象和Request對象的clone方法。它必須在body還沒有讀取前調(diào)用,返回一個前的body,也就是說,需要使用幾次body,就要調(diào)用幾次clone方法。
addEventListener('fetch', function(evt) { var sheep = new Response("Dolly"); console.log(sheep.bodyUsed); // false var clone = sheep.clone(); console.log(clone.bodyUsed); // false clone.text(); console.log(sheep.bodyUsed); // false console.log(clone.bodyUsed); // true evt.respondWith(cache.add(sheep.clone()).then(function(e) { return sheep; }); });
MDN, Using XMLHttpRequest
Eric Bidelman, New Tricks in XMLHttpRequest2
Matt West,
Monsur Hossain, Using CORS
Matt Gaunt, Introduction to fetch()
Nikhil Marathe, This API is so Fetching!
Ludovico Fischer, Introduction to the Fetch API