fetch使用的常見問題及解決辦法 fetch使用的常見問題及解決辦法
fetch使用的常見問題及解決辦法
首先宣告一下,本文不是要講解fetch的具體用法,不清楚的可以參考MDN fetch教程。
引言
說道fetch就不得不提XMLHttpRequest了,XHR在傳送web請求時需要開發者配置相關請求資訊和成功後的回撥,儘管開發者只關心請求成功後的業務處理,但是也要配置其他繁瑣內容,導致配置和呼叫比較混亂,也不符合關注分離的原則;fetch的出現正是為了解決XHR存在的這些問題。例如下面程式碼:
fetch(url).then(function(response) {
return response.json();
}).then(function(data) {
console.log(data);
}).catch(function(e) {
console.log("Oops, error");
});
上面這段程式碼讓開發者只關注請求成功後的業務邏輯處理,其他的不用關心,相當簡單;也比較符合現代Promise形式,比較友好。
fetch是基於Promise設計的,從上面程式碼也能看得出來,這就要求fetch要配合Promise一起使用。正是這種設計,fetch所帶來的優點正如傳統 Ajax 已死,Fetch 永生總結的一樣:
-
語法簡單,更加語義化
-
基於標準的Promise實現,支援async/await
-
使用
isomorphic-fetch
可以方便同構
不過話說回來,fetch雖然有很多優點,但是使用fetch來進行專案開發時,也是有一些常見問題的,下面就來說說fetch使用的常見問題。
fetch相容性
fetch是相對較新的技術,當然就會存在瀏覽器相容性的問題,借用上面應用文章的一幅圖加以說明fetch在各種瀏覽器的原生支援情況:
從上圖可以看出,在各個瀏覽器低版本的情況下都是不被支援的。
那麼問題來了,如何在所有瀏覽器中通用fetch呢,當然就要考慮fetch的polyfill了。
上面說過,fetch是基於Promise來實現的,所以在低版本瀏覽器中Promise可能也未被原生支援,所以還需要Promise的polyfill;大多數情況下,實現fetch的polyfill需要涉及到的:
- promise的polyfill,例如es6-promise、babel-polyfill提供的promise實現。
- fetch的polyfill實現,例如isomorphic-fetch和whatwg-fetch
這樣是否就可以安全的使用fetch來進行前後端通訊了?上面說了在大多數情況下是這樣,但是IE8/9則比較特殊:IE8它使用的是ES3,而IE9則對ES5部分支援。這種情況下還需要ES5的polyfill es5-shim
支援了。
上述有關promise的polyfill實現,需要說明的是:
babel-runtime是不能作為Promise的polyfill的實現的,否則在IE8/9下使用fetch會報
Promise未定義
。為什麼?我想大家猜到了,因為babel-runtime實現的polyfill是區域性實現而不是全域性實現,fetch底層實現用到Promise就是從全域性中去取的,拿不到這報上述錯誤。
另外,順便補充一下fetch的polyfill實現思路是:
首先判斷瀏覽器是否原生支援fetch,否則結合Promise使用XMLHttpRequest的方式來實現;這正是
whatwg-fetch
的實現思路,而同構應用中使用的isomorphic-fetch
,其客戶端fetch的實現是直接require whatwg-fetch來實現的。
fetch預設不攜帶cookie
fetch傳送請求預設是不傳送cookie的,不管是同域還是跨域;那麼問題就來了,對於那些需要許可權驗證的請求就可能無法正常獲取資料,這時可以配置其credentials
項,其有3個值:
-
omit
: 預設值,忽略cookie的傳送 -
same-origin
: 表示cookie只能同域傳送,不能跨域傳送 -
include
: cookie既可以同域傳送,也可以跨域傳送
credentials
所表達的含義,其實與XHR2中的withCredentials
屬性類似,表示請求是否攜帶cookie;具體可以參考阮一峰老師的跨域資源共享 CORS 詳解中withCredentials一節的介紹;
這樣,若要fetch請求攜帶cookie資訊,只需設定一下credentials選項即可,例如fetch(url, {credentials: 'include'})
;
另外補充一點:
fetch預設對服務端通過
Set-Cookie
頭設定的cookie也會忽略,若想選擇接受來自服務端的cookie資訊,也必須要配置credentials選項;
fetch請求對某些錯誤http狀態不會reject
這主要是由fetch返回promise導致的,因為fetch返回的promise在某些錯誤的http狀態下如400、500等不會reject,相反它會被resolve;只有網路錯誤會導致請求不能完成時,fetch 才會被 reject;所以一般會對fetch請求做一層封裝,例如下面程式碼所示:
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}
const error = new Error(response.statusText);
error.response = response;
throw error;
}
function parseJSON(response) {
return response.json();
}
export default function request(url, options) {
let opt = options||{};
return fetch(url, {credentials: 'include', ...opt})
.then(checkStatus)
.then(parseJSON)
.then((data) => ( data ))
.catch((err) => ( err ));
}
fetch不支援超時timeout處理
用過fetch的都知道,fetch不像大多數ajax庫那樣對請求設定超時timeout,它沒有有關請求超時的feature,這一點比較蛋疼。所以在fetch標準新增超時feature之前,都需要polyfill該特性。
實際上,我們真正需要的是abort()
, timeout可以通過timeout+abort
方式來實現,起到真正超時丟棄當前的請求。
而在目前的fetch指導規範中,fetch並不是一個具體例項,而只是一個方法;其返回的promise例項根據Promise指導規範標準是不能abort的,也不能手動改變promise例項的狀態,只能由內部來根據請求結果來改變promise的狀態。
既然不能手動控制fetch方法執行後返回的promise例項狀態,那麼是不是可以建立一個可以手動控制狀態的新Promise例項呢。所以:
實現fetch的timeout功能,其思想就是新建立一個可以手動控制promise狀態的例項,根據不同情況來對新promise例項進行resolve或者reject,從而達到實現timeout的功能;
根據github上timeout handling上的討論,目前可以有兩種不同的解決方法:
方法一:單純setTimeout方式
var oldFetchfn = fetch; //攔截原始的fetch方法
window.fetch = function(input, opts){//定義新的fetch方法,封裝原有的fetch方法
return new Promise(function(resolve, reject){
var timeoutId = setTimeout(function(){
reject(new Error("fetch timeout"))
}, opts.timeout);
oldFetchfn(input, opts).then(
res=>{
clearTimeout(timeoutId);
resolve(res)
},
err=>{
clearTimeout(timeoutId);
reject(err)
}
)
})
}
當然在上面基礎上可以模擬類似XHR的abort
功能:
var oldFetchfn = fetch;
window.fetch = function(input, opts){
return new Promise(function(resolve, reject){
var abort_promise = function(){
reject(new Error("fetch abort"))
};
var p = oldFetchfn(input, opts).then(resolve, reject);
p.abort = abort_promise;
return p;
})
}
方法二:利用Promise.race方法
Promise.race方法接受一個promise例項陣列引數,表示多個promise例項中任何一個最先改變狀態,那麼race方法返回的promise例項狀態就跟著改變,具體可以參考這裡。
var oldFetchfn = fetch; //攔截原始的fetch方法
window.fetch = function(input, opts){//定義新的fetch方法,封裝原有的fetch方法
var fetchPromise = oldFetchfn(input, opts);
var timeoutPromise = new Promise(function(resolve, reject){
setTimeout(()=>{
reject(new Error("fetch timeout"))
}, opts.timeout)
});
retrun Promise.race([fetchPromise, timeoutPromise])
}
最後,對fetch的timeout的上述實現方式補充幾點:
timeout不是請求連線超時的含義,它表示請求的response時間,包括請求的連線、伺服器處理及伺服器響應回來的時間;
fetch的timeout即使超時發生了,本次請求也不會被abort丟棄掉,它在後臺仍然會發送到伺服器端,只是本次請求的響應內容被丟棄而已;
fetch不支援JSONP
fetch是與伺服器端進行非同步互動的,而JSONP是外鏈一個javascript資源,並不是真正ajax,所以fetch與JSONP沒有什麼直接關聯,當然至少目前是不支援JSONP的。
這裡我們把JSONP與fetch關聯在一起有點差強人意,fetch只是一個ajax庫,我們不可能使fetch支援JSONP;只是我們要實現一個JSONP,只不過這個JSONP的實現要與fetch的實現類似,即基於Promise來實現一個JSONP
;而其外在表現給人感覺是fetch支援JSONP一樣;
目前比較成熟的開源JSONP實現fetch-jsonp給我們提供瞭解決方案,想了解可以自行前往。不過再次想嘮叨一下其JSONP的實現步驟,因為在本人面試的前端候選人中大部分人對JSONP的實現語焉不詳;
使用它非常簡單,首先需要用npm安裝fetch-jsonp
npm install fetch-jsonp --save-dev
然後在像下面一樣使用:
fetchJsonp('/users.jsonp', {
timeout: 3000,
jsonpCallback: 'custom_callback'
})
.then(function(response) {
return response.json()
}).catch(function(ex) {
console.log('parsing failed', ex)
})
fetch不支援progress事件
XHR是原生支援progress事件的,例如下面程式碼這樣:
var xhr = new XMLHttpRequest()
xhr.open('POST', '/uploads')
xhr.onload = function() {}
xhr.onerror = function() {}
function updateProgress (event) {
if (event.lengthComputable) {
var percent = Math.round((event.loaded / event.total) * 100)
console.log(percent)
}
xhr.upload.onprogress =updateProgress; //上傳的progress事件
xhr.onprogress = updateProgress; //下載的progress事件
}
xhr.send();
但是fetch是不支援有關progress
事件的;不過可喜的是,根據fetch的指導規範標準,其內部設計實現了Request
和Response
類;其中Response封裝一些方法和屬性,通過Response例項可以訪問這些方法和屬性,例如response.json()
、response.body
等等;
值得關注的地方是,response.body
是一個可讀位元組流物件,其實現了一個getRender()
方法,其具體作用是:
getRender()
方法用於讀取響應的原始位元組流,該位元組流是可以迴圈讀取的,直至body內容傳輸完成;
因此,利用到這點可以模擬出fetch的progress,具體可以參考這篇文章2016 - the year of web streams。
程式碼實現如下,線上demo請參考fetch progress demo。
// fetch() returns a promise that resolves once headers have been received
fetch(url).then(response => {
// response.body is a readable stream.
// Calling getReader() gives us exclusive access to the stream's content
var reader = response.body.getReader();
var bytesReceived = 0;
// read() returns a promise that resolves when a value has been received
reader.read().then(function processResult(result) {
// Result objects contain two properties:
// done - true if the stream has already given you all its data.
// value - some data. Always undefined when done is true.
if (result.done) {
console.log("Fetch complete");
return;
}
// result.value for fetch streams is a Uint8Array
bytesReceived += result.value.length;
console.log('Received', bytesReceived, 'bytes of data so far');
// Read some more, and call this function again
return reader.read().then(processResult);
});
});
另外,github上也有使用Promise+XHR
結合的方式實現類fetch的progress效果(當然這跟fetch完全不搭邊)可以參考這裡,具體程式碼如下:
function fetchProgress(url, opts={}, onProgress){
return new Promise(funciton(resolve, reject){
var xhr = new XMLHttpRequest();
xhr.open(opts.method || 'get', url);
for(var key in opts.headers || {}){
xhr.setRequestHeader(key, opts.headers[key]);
}
xhr.onload = e => resolve(e.target.responseText)
xhr.onerror = reject;
if (xhr.upload && onProgress){
xhr.upload.onprogress = onProgress; //上傳
}
if ('onprogerss' in xhr && onProgress){
xhr.onprogress = onProgress; //下載
}
xhr.send(opts.body)
})
}
fetchProgress('/upload').then(console.log)
fetch跨域問題
既然是ajax庫,就不可避免與跨域扯上關係;XHR2是支援跨域請求的,只不過要滿足瀏覽器端支援CORS
,伺服器通過Access-Control-Allow-Origin
來允許指定的源進行跨域,僅此一種方式。
與XHR2一樣,fetch也是支援跨域請求的,只不過其跨域請求做法與XHR2一樣,需要客戶端與服務端支援;另外,fetch還支援一種跨域,不需要伺服器支援的形式,具體可以通過其mode
的配置項來說明。
fetch的mode
配置項有3個值,如下:
-
same-origin
:該模式是不允許跨域的,它需要遵守同源策略,否則瀏覽器會返回一個error告知不能跨域;其對應的response type為basic
。 -
cors
: 該模式支援跨域請求,顧名思義它是以CORS的形式跨域;當然該模式也可以同域請求不需要後端額外的CORS支援;其對應的response type為cors
。 -
no-cors
: 該模式用於跨域請求但是伺服器不帶CORS響應頭,也就是服務端不支援CORS;這也是fetch的特殊跨域請求方式;其對應的response type為opaque
。
針對跨域請求,cors模式是常見跨域請求實現,但是fetch自帶的no-cors
跨域請求模式則較為陌生,該模式有一個比較明顯的特點:
該模式允許瀏覽器傳送本次跨域請求,但是不能訪問響應返回的內容,這也是其response type為opaque透明的原因。
這與<img/>
傳送的請求類似,只是該模式不能訪問響應的內容資訊;但是它可以被其他APIs進行處理,例如ServiceWorker。另外,該模式返回的repsonse可以在Cache API中被儲存起來以便後續的對它的使用,這點對script、css和圖片的CDN資源是非常合適的,因為這些資源響應頭中都沒有CORS頭。
總的來說,fetch的跨域請求是使用CORS方式,需要瀏覽器和服務端的支援。
參考文獻
首先宣告一下,本文不是要講解fetch的具體用法,不清楚的可以參考MDN fetch教程。
引言
說道fetch就不得不提XMLHttpRequest了,XHR在傳送web請求時需要開發者配置相關請求資訊和成功後的回撥,儘管開發者只關心請求成功後的業務處理,但是也要配置其他繁瑣內容,導致配置和呼叫比較混亂,也不符合關注分離的原則;fetch的出現正是為了解決XHR存在的這些問題。例如下面程式碼:
fetch(url).then(function(response) {
return response.json();
}).then(function(data) {
console.log(data);
}).catch(function(e) {
console.log("Oops, error");
});
上面這段程式碼讓開發者只關注請求成功後的業務邏輯處理,其他的不用關心,相當簡單;也比較符合現代Promise形式,比較友好。
fetch是基於Promise設計的,從上面程式碼也能看得出來,這就要求fetch要配合Promise一起使用。正是這種設計,fetch所帶來的優點正如傳統 Ajax 已死,Fetch 永生總結的一樣:
-
語法簡單,更加語義化
-
基於標準的Promise實現,支援async/await
-
使用
isomorphic-fetch
可以方便同構
不過話說回來,fetch雖然有很多優點,但是使用fetch來進行專案開發時,也是有一些常見問題的,下面就來說說fetch使用的常見問題。
fetch相容性
fetch是相對較新的技術,當然就會存在瀏覽器相容性的問題,借用上面應用文章的一幅圖加以說明fetch在各種瀏覽器的原生支援情況:
從上圖可以看出,在各個瀏覽器低版本的情況下都是不被支援的。
那麼問題來了,如何在所有瀏覽器中通用fetch呢,當然就要考慮fetch的polyfill了。
上面說過,fetch是基於Promise來實現的,所以在低版本瀏覽器中Promise可能也未被原生支援,所以還需要Promise的polyfill;大多數情況下,實現fetch的polyfill需要涉及到的:
- promise的polyfill,例如es6-promise、babel-polyfill提供的promise實現。
- fetch的polyfill實現,例如isomorphic-fetch和whatwg-fetch
這樣是否就可以安全的使用fetch來進行前後端通訊了?上面說了在大多數情況下是這樣,但是IE8/9則比較特殊:IE8它使用的是ES3,而IE9則對ES5部分支援。這種情況下還需要ES5的polyfill es5-shim
支援了。
上述有關promise的polyfill實現,需要說明的是:
babel-runtime是不能作為Promise的polyfill的實現的,否則在IE8/9下使用fetch會報
Promise未定義
。為什麼?我想大家猜到了,因為babel-runtime實現的polyfill是區域性實現而不是全域性實現,fetch底層實現用到Promise就是從全域性中去取的,拿不到這報上述錯誤。
另外,順便補充一下fetch的polyfill實現思路是:
首先判斷瀏覽器是否原生支援fetch,否則結合Promise使用XMLHttpRequest的方式來實現;這正是
whatwg-fetch
的實現思路,而同構應用中使用的isomorphic-fetch
,其客戶端fetch的實現是直接require whatwg-fetch來實現的。
fetch預設不攜帶cookie
fetch傳送請求預設是不傳送cookie的,不管是同域還是跨域;那麼問題就來了,對於那些需要許可權驗證的請求就可能無法正常獲取資料,這時可以配置其credentials
項,其有3個值:
-
omit
: 預設值,忽略cookie的傳送 -
same-origin
: 表示cookie只能同域傳送,不能跨域傳送 -
include
: cookie既可以同域傳送,也可以跨域傳送
credentials
所表達的含義,其實與XHR2中的withCredentials
屬性類似,表示請求是否攜帶cookie;具體可以參考阮一峰老師的跨域資源共享 CORS 詳解中withCredentials一節的介紹;
這樣,若要fetch請求攜帶cookie資訊,只需設定一下credentials選項即可,例如fetch(url, {credentials: 'include'})
;
另外補充一點:
fetch預設對服務端通過
Set-Cookie
頭設定的cookie也會忽略,若想選擇接受來自服務端的cookie資訊,也必須要配置credentials選項;
fetch請求對某些錯誤http狀態不會reject
這主要是由fetch返回promise導致的,因為fetch返回的promise在某些錯誤的http狀態下如400、500等不會reject,相反它會被resolve;只有網路錯誤會導致請求不能完成時,fetch 才會被 reject;所以一般會對fetch請求做一層封裝,例如下面程式碼所示:
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}
const error = new Error(response.statusText);
error.response = response;
throw error;
}
function parseJSON(response) {
return response.json();
}
export default function request(url, options) {
let opt = options||{};
return fetch(url, {credentials: 'include', ...opt})
.then(checkStatus)
.then(parseJSON)
.then((data) => ( data ))
.catch((err) => ( err ));
}
fetch不支援超時timeout處理
用過fetch的都知道,fetch不像大多數ajax庫那樣對請求設定超時timeout,它沒有有關請求超時的feature,這一點比較蛋疼。所以在fetch標準新增超時feature之前,都需要polyfill該特性。
實際上,我們真正需要的是abort()
, timeout可以通過timeout+abort
方式來實現,起到真正超時丟棄當前的請求。
而在目前的fetch指導規範中,fetch並不是一個具體例項,而只是一個方法;其返回的promise例項根據Promise指導規範標準是不能abort的,也不能手動改變promise例項的狀態,只能由內部來根據請求結果來改變promise的狀態。
既然不能手動控制fetch方法執行後返回的promise例項狀態,那麼是不是可以建立一個可以手動控制狀態的新Promise例項呢。所以:
實現fetch的timeout功能,其思想就是新建立一個可以手動控制promise狀態的例項,根據不同情況來對新promise例項進行resolve或者reject,從而達到實現timeout的功能;
根據github上timeout handling上的討論,目前可以有兩種不同的解決方法:
方法一:單純setTimeout方式
var oldFetchfn = fetch; //攔截原始的fetch方法
window.fetch = function(input, opts){//定義新的fetch方法,封裝原有的fetch方法
return new Promise(function(resolve, reject){
var timeoutId = setTimeout(function(){
reject(new Error("fetch timeout"))
}, opts.timeout);
oldFetchfn(input, opts).then(
res=>{
clearTimeout(timeoutId);
resolve(res)
},
err=>{
clearTimeout(timeoutId);
reject(err)
}
)
})
}
當然在上面基礎上可以模擬類似XHR的abort
功能:
var oldFetchfn = fetch;
window.fetch = function(input, opts){
return new Promise(function(resolve, reject){
var abort_promise = function(){
reject(new Error("fetch abort"))
};
var p = oldFetchfn(input, opts).then(resolve, reject);
p.abort = abort_promise;
return p;
})
}
方法二:利用Promise.race方法
Promise.race方法接受一個promise例項陣列引數,表示多個promise例項中任何一個最先改變狀態,那麼race方法返回的promise例項狀態就跟著改變,具體可以參考這裡。
var oldFetchfn = fetch; //攔截原始的fetch方法
window.fetch = function(input, opts){//定義新的fetch方法,封裝原有的fetch方法
var fetchPromise = oldFetchfn(input, opts);
var timeoutPromise = new Promise(function(resolve, reject){
setTimeout(()=>{
reject(new Error("fetch timeout"))
}, opts.timeout)
});
retrun Promise.race([fetchPromise, timeoutPromise])
}
最後,對fetch的timeout的上述實現方式補充幾點:
timeout不是請求連線超時的含義,它表示請求的response時間,包括請求的連線、伺服器處理及伺服器響應回來的時間;
fetch的timeout即使超時發生了,本次請求也不會被abort丟棄掉,它在後臺仍然會發送到伺服器端,只是本次請求的響應內容被丟棄而已;
fetch不支援JSONP
fetch是與伺服器端進行非同步互動的,而JSONP是外鏈一個javascript資源,並不是真正ajax,所以fetch與JSONP沒有什麼直接關聯,當然至少目前是不支援JSONP的。
這裡我們把JSONP與fetch關聯在一起有點差強人意,fetch只是一個ajax庫,我們不可能使fetch支援JSONP;只是我們要實現一個JSONP,只不過這個JSONP的實現要與fetch的實現類似,即基於Promise來實現一個JSONP
;而其外在表現給人感覺是fetch支援JSONP一樣;
目前比較成熟的開源JSONP實現fetch-jsonp給我們提供瞭解決方案,想了解可以自行前往。不過再次想嘮叨一下其JSONP的實現步驟,因為在本人面試的前端候選人中大部分人對JSONP的實現語焉不詳;
使用它非常簡單,首先需要用npm安裝fetch-jsonp
npm install fetch-jsonp --save-dev
然後在像下面一樣使用:
fetchJsonp('/users.jsonp', {
timeout: 3000,
jsonpCallback: 'custom_callback'
})
.then(function(response) {
return response.json()
}).catch(function(ex) {
console.log('parsing failed', ex)
})
fetch不支援progress事件
XHR是原生支援progress事件的,例如下面程式碼這樣:
var xhr = new XMLHttpRequest()
xhr.open('POST', '/uploads')
xhr.onload = function() {}
xhr.onerror = function() {}
function updateProgress (event) {
if (event.lengthComputable) {
var percent = Math.round((event.loaded / event.total) * 100)
console.log(percent)
}
xhr.upload.onprogress =updateProgress; //上傳的progress事件
xhr.onprogress = updateProgress; //下載的progress事件
}
xhr.send();
但是fetch是不支援有關progress
事件的;不過可喜的是,根據fetch的指導規範標準,其內部設計實現了Request
和Response
類;其中Response封裝一些方法和屬性,通過Response例項可以訪問這些方法和屬性,例如response.json()
、response.body
等等;
值得關注的地方是,response.body
是一個可讀位元組流物件,其實現了一個getRender()
方法,其具體作用是:
getRender()
方法用於讀取響應的原始位元組流,該位元組流是可以迴圈讀取的,直至body內容傳輸完成;
因此,利用到這點可以模擬出fetch的progress,具體可以參考這篇文章2016 - the year of web streams。
程式碼實現如下,線上demo請參考fetch progress demo。
// fetch() returns a promise that resolves once headers have been received
fetch(url).then(response => {
// response.body is a readable stream.
// Calling getReader() gives us exclusive access to the stream's content
var reader = response.body.getReader();
var bytesReceived = 0;
// read() returns a promise that resolves when a value has been received
reader.read().then(function processResult(result) {
// Result objects contain two properties:
// done - true if the stream has already given you all its data.
// value - some data. Always undefined when done is true.
if (result.done) {
console.log("Fetch complete");
return;
}
// result.value for fetch streams is a Uint8Array
bytesReceived += result.value.length;
console.log('Received', bytesReceived, 'bytes of data so far');
// Read some more, and call this function again
return reader.read().then(processResult);
});
});
另外,github上也有使用Promise+XHR
結合的方式實現類fetch的progress效果(當然這跟fetch完全不搭邊)可以參考這裡,具體程式碼如下:
function fetchProgress(url, opts={}, onProgress){
return new Promise(funciton(resolve, reject){
var xhr = new XMLHttpRequest();
xhr.open(opts.method || 'get', url);
for(var key in opts.headers || {}){
xhr.setRequestHeader(key, opts.headers[key]);
}
xhr.onload = e => resolve(e.target.responseText)
xhr.onerror = reject;
if (xhr.upload && onProgress){
xhr.upload.onprogress = onProgress; //上傳
}
if ('onprogerss' in xhr && onProgress){
xhr.onprogress = onProgress; //下載
}
xhr.send(opts.body)
})
}
fetchProgress('/upload').then(console.log)
fetch跨域問題
既然是ajax庫,就不可避免與跨域扯上關係;XHR2是支援跨域請求的,只不過要滿足瀏覽器端支援CORS
,伺服器通過Access-Control-Allow-Origin
來允許指定的源進行跨域,僅此一種方式。
與XHR2一樣,fetch也是支援跨域請求的,只不過其跨域請求做法與XHR2一樣,需要客戶端與服務端支援;另外,fetch還支援一種跨域,不需要伺服器支援的形式,具體可以通過其mode
的配置項來說明。
fetch的mode
配置項有3個值,如下:
-
same-origin
:該模式是不允許跨域的,它需要遵守同源策略,否則瀏覽器會返回一個error告知不能跨域;其對應的response type為basic
。 -
cors
: 該模式支援跨域請求,顧名思義它是以CORS的形式跨域;當然該模式也可以同域請求不需要後端額外的CORS支援;其對應的response type為cors
。 -
no-cors
: 該模式用於跨域請求但是伺服器不帶CORS響應頭,也就是服務端不支援CORS;這也是fetch的特殊跨域請求方式;其對應的response type為opaque
。
針對跨域請求,cors模式是常見跨域請求實現,但是fetch自帶的no-cors
跨域請求模式則較為陌生,該模式有一個比較明顯的特點:
該模式允許瀏覽器傳送本次跨域請求,但是不能訪問響應返回的內容,這也是其response type為opaque透明的原因。
這與<img/>
傳送的請求類似,只是該模式不能訪問響應的內容資訊;但是它可以被其他APIs進行處理,例如ServiceWorker。另外,該模式返回的repsonse可以在Cache API中被儲存起來以便後續的對它的使用,這點對script、css和圖片的CDN資源是非常合適的,因為這些資源響應頭中都沒有CORS頭。
總的來說,fetch的跨域請求是使用CORS方式,需要瀏覽器和服務端的支援。