標籤:
寫在前面
fetch 同 XMLHttpRequest 非常類似,都是用來做網路請求。但是同複雜的XMLHttpRequest的API相比,fetch使用了Promise,這讓它使用起來更加簡潔,從而避免陷入”回調地獄”。
兩者比較
比如,如果我們想要實現這樣一個需求:請求一個URL地址,擷取響應資料並將資料轉換成JSON格式。使用fetch和XMLHttpRequest實現的方式是不同的。
使用
XMLHttpRequest實現
使用XMLHttpRequest來實現改功能需要設定兩個監聽函數,分別用來處理成功和失敗的情況,同時還需要依次調用open()和send()方法才能實現請求。
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實現
使用fetch來實現是這樣的:
fetch(‘./api/some.json‘) .then(function(res) { if (res.status !== 200) { console.log(‘Looks like there was a problem. Status Code: ‘ + res.status); return; } // 處理響應中的文本資訊 res.json().then(function(data) { console.log(data); }); }) .catch(function(err) { console.log(‘Fetch Error : %S‘, err); })
在將響應的文本資訊轉換成JSON格式前,需要先確保響應的狀態代碼為200。
fetch()請求後返回的響應是一個stream對象,這就意味著我們在調用json()方法後會返回一個Promise,因為讀取stream的過程是非同步作業的。
響應中的中繼資料
在上面的例子中,我們可以查看響應對象的狀態代碼,也知道了如何將響應轉換成JSON格式的資料。但其實我們可以訪問的中繼資料還有以下這些:
fetch("users.json").then(function(res) { console.log(res.headers.get(‘Content-Type‘)); console.log(res.headers.get(‘Data‘)); console.log(res.status); console.log(res.statusText); console.log(res.type); console.log(res.url);})
響應類型
當我們發送fetch請求時,返回的res.type可能是basic、cors或opaque中的一種。這些類型可以告知我們資源從何而來,這樣就能知道該如何處理響應對象。
當我們請求的是同一域下的資源時,響應返回的類型為basic,此時沒有任何限制,我們可以查看響應中的任何資料。
如何請求的是跨域資源,那麼會返回一個CORS類型的頭部,並且響應類型為cors。這種類型跟上面的basic非常相似,只是它對回應標頭部的欄位訪問有限制,你只可以訪問這些屬性:
- Cache-Control
- Content-Language
- Content-Type
- Expires
- Last-Modified
- Pragma
Opaque 類型的響應也是訪問跨域資源的時候產生的,只是回應標頭不是CORS類型的回應標頭而已。如果是這種類型的響應,那麼我們就不能讀取返回的資料,也不能查看請求的狀態代碼,這就是意味著我們將無法確定請求是成功了還是失敗了。目前在fetch()的實現中,無法請求跨域的資源。
我們可以為fetch請求定義mode屬性,來保證只有合格請求才會被處理。可以設定的mode屬性值如下:
same-origin 只有請求相同域下的資源才能成功,其他請求均被拒絕。
cors 允許請求同域或者跨域資源,但是跨域必須返回相應的跨域要求標頭部。
cors-with-forced-preflight 在發出實際請求前先做preflight檢查。
no-cors 針對跨域資源做請求,但是不返回CORS的回應標頭,這是屬於opaque類型的響應(window下無法使用)
在使用mode時,需要將fetch請求的第二個參數作為設定物件,並在其中配置具體的模式,如下代碼:
fetch("http://some-site.com/cors-enabled/some.json",{mode: ‘cors‘}) .then(function(res){ return res.text(); }) .then(function(text) { console.log(‘Request successfully‘, text); }) .catch(function(err) { console.log(‘Request failed‘, error) })
Promise 方法鏈
Promise 的特性之一就是可以實現鏈式調用,fetch也可以使用該特性,同時,使用鏈式調用可以讓請求的處理邏輯更加通用。
如果使用介面反饋的JSON格式資料,那麼針對每次響應,我們都需要檢查響應狀態並做JSON格式轉換。其實還能簡化代碼,那就是把狀態監測和JSON轉換的代碼放到單獨的函數中去。比如:
function status(res) { if (res.status >= 200 && res.status < 300) { return Promise.resolve(response) } else { return Promise.reject(new Error(res.statusText)) }}function json(res) { return res.json()}fetch(‘user.json‘) .then(status) .then(json) .then(function(data) { console.log(‘Request succeeded with JSON response‘, data); }) .catch(function(err) { console.log(‘Request failed‘, error); })
在上面的代碼中,我們定義了一個status方法來檢查response.status 的狀態,根據結果不同返回Promise.resolve() 或 Promise.reject() ,這是fetch()方法鏈中的第一個方法調用。如果返回resolve狀態,我們會繼續調用json(),從而返回response.json()的執行結果。經過這些處理完我們已經可以擷取解析後的JSON對象。如果解析失敗,Promise返回reject狀態,執行catch裡的代碼進行錯誤處理。
這麼編碼更大的好處在於,你可以在所有的fetch請求中使用上面的邏輯代碼,從而讓代碼變得更加容易閱讀、維護和測試。
使用
fetch 請求發送憑證資訊
如果我們想在fetch請求中帶一些憑證資訊,比如cookie等,我們應該將請求中的credentials設定為include:
fetch(url, { credentials: ‘include‘})
注意: 伺服器返回 400,500 錯誤碼時並不會 reject,只有網路錯誤這些導致請求不能完成時,fetch 才會被 reject。
Response相關屬性及方法
bodyUsed
- 標記傳回值是否被使用過
- 這樣設計的目的是為了之後相容基於流的API,讓應用一次消費data,這樣就允許了JavaScript處理大檔案例如視頻,並且可以支援即時壓縮和編輯。
fetch(‘/test/content.json‘).then(function(res){ console.log(res.bodyUsed); // false var data = res.json(); console.log(res.bodyUsed); //true return data;}).then(function(data){ console.log(data);}).catch(function(error){ console.log(error);});
headers
返回Headers對象,該對象實現了Iterator,可通過for…of遍曆
fetch(‘/test/content.json‘).then(function(res){ var headers = res.headers; console.log(headers.get(‘Content-Type‘)); // application/json console.log(headers.has(‘Content-Type‘)); // true console.log(headers.getAll(‘Content-Type‘)); // ["application/json"] for(let key of headers.keys()){ console.log(key); // datelast-modified server accept-ranges etag content-length content-type } for(let value of headers.values()){ console.log(value); } headers.forEach(function(value, key, arr){ console.log(value); // 對應values()的傳回值 console.log(key); // 對應keys()的傳回值 }); return res.json();}).then(function(data){ console.log(data);}).catch(function(error){ console.log(error);});
ok
- 是否正常返回
- 代表狀態代碼在200-299之間
status
狀態代碼( 200表示 成功)
statusText
狀態原因 (‘OK’表示 成功)
type
basic:正常的,同域的請求,包含所有的headers。排除Set-Cookie和Set-Cookie2。
cors:Response從一個合法的跨域請求獲得,一部分header和body可讀。
error:網路錯誤。Response的status是0,Headers是空的並且不可寫。當Response是從Response.error()中得到時,就是這種類型。
opaque: Response從"no-cors"請求了跨域資源。依靠Server端來做限制。
url
返回完整的url字串。如:’http://xxx.com/xx?a=1’
arrayBuffer()
返回ArrayBuffer類型的資料的Promise對象
blob()
返回Blob類型的資料的Promise對象
clone()
- 產生一個
Response的複製
body只能被讀取一次,但clone方法就可以得到body的一個備份
- 複製體仍然具有
bodyUsed屬性,如果被使用過一次,依然會失效
fetch(‘/test/content.json‘).then(function(data){ var d = data.clone(); d.text().then(function(text){ console.log(JSON.parse(text)); }); return data.json();}).then(function(data){ console.log(data);}).catch(function(error){ console.log(error);});
json()
返回JSON類型的資料的Promise對象
text()
返回Text類型的資料的Promise對象
formData()
返回FormData類型的資料的Promise對象
fetch基本用法
get方式
fetch(‘/test/content.json‘).then(function(data){ return data.json();}).then(function(data){ console.log(data);}).catch(function(error){ console.log(error);});
post方式
fetch(‘/test/content.json‘, { // url: fetch事實標準中可以通過Request相關api進行設定 method: ‘POST‘, mode: ‘same-origin‘, // same-origin|no-cors(預設)|cors credentials: ‘include‘, // omit(預設,不帶cookie)|same-origin(同源帶cookie)|include(總是帶cookie) headers: { // headers: fetch事實標準中可以通過Header相關api進行設定 ‘Content-Type‘: ‘application/x-www-form-urlencoded‘ // default: ‘application/json‘ }, body: ‘a=1&b=2‘ // body: fetch事實標準中可以通過Body相關api進行設定}).then(function(res){ res: fetch事實標準中可以通過Response相關api進行設定 return res.json();}).then(function(data){ console.log(data);}).catch(function(error){});
fetch不足之處無法監控讀取進度和插斷要求
Promises缺少了一些重要的XMLHttpRequest的使用情境。例如, 使用標準的ES6 Promise你無法收集進入資訊或插斷要求。而Fetch的狂熱開發人員更是試圖提供Promise API的擴充用於取消一個Promise。 這個提議有點自挖牆角的意思,因為將這將讓Promise變得不符合標準。但這個提議或許會導致未來出現一個可取消的Promise標準。 但另一方面,使用XMLHttpRequest你可以類比進度(監聽progress事件),也可以取消請求(使用abort()方法)。 但是,如果有必要你也可以使用Promise來包裹它
瀏覽器支援
目前Chrome 42+, Opera 29+, 和Firefox 39+都支援Fetch。微軟也考慮在未來的版本中支援Fetch。 諷刺的是,當IE瀏覽器終於微響應實現了progress事件的時候,XMLHttpRequest也走到了盡頭。 目前,如果你需要支援IE的話,你需要使用一個polyfill庫。
@參考 深入淺出Fetch API
fetch和XMLHttpRequest講解