fetch號稱是ajax的替代品,它的API是基于Promise設(shè)計的,舊版本的瀏覽器不支持 Promise。
關(guān)于fetch的用法 ,本文就不做介紹了,可以參看官方文檔,可以得到很詳細的介紹 使用Fetch - Web API 接口參考 | MDN
Fetch的代碼結(jié)構(gòu)比起ajax簡單多了,參數(shù)有點像jQuery ajax。但是,一定記住fetch不是ajax的進一步封裝,而是原生js,沒有使用XMLHttpRequest對象。
fetch的優(yōu)點:
- 符合關(guān)注分離,沒有將輸入、輸出和用事件來跟蹤的狀態(tài)混雜在一個對象里
- 更好更方便的寫法,坦白說,上面的理由對我來說完全沒有什么說服力,因為不管是Jquery還是Axios都已經(jīng)幫我們把xhr封裝的足夠好,使用起來也足夠方便,為什么我們還要花費大力氣去學習fetch?
我認為fetch的優(yōu)勢就是
- 語法簡潔,更加語義化
- 基于標準 Promise 實現(xiàn),支持 async/await
- 同構(gòu)方便,使用 isomorphic-fetch
- 更加底層,提供的API豐富(request, response)
- 脫離了XHR,是ES規(guī)范里新的實現(xiàn)方式,是js原生方法不需要引入額外的庫。
近在使用fetch的時候,也遇到了不少的問題:fetch是一個低層次的API,你可以把它考慮成原生的XHR,所以使用起來并不是那么舒服,需要進行封裝。
例如:
- fetch只對網(wǎng)絡(luò)請求報錯,對400,500都當做成功的請求,服務(wù)器返回 400,500 錯誤碼時并不會 reject,只有網(wǎng)絡(luò)錯誤這些導致請求不能完成時,fetch 才會被 reject。
- fetch默認不會帶cookie,需要添加配置項: fetch(url, {credentials: 'include'})
- fetch不支持abort,不支持超時控制。
- fetch沒有辦法原生監(jiān)測請求的進度,而XHR可以
正對上面幾個問題,我們下面分別來分析,并對其問題進行處理:
1. fetch請求對某些錯誤http狀態(tài)不會reject
這主要是由fetch返回promise導致的,因為fetch返回的promise在某些錯誤的http狀態(tài)下如400、500等不會reject,相反它會被resolve;只有網(wǎng)絡(luò)錯誤會導致請求不能完成時,fetch 才會被 reject;所以一般會對fetch請求做一層封裝。 簡單的理解就是 fetch 只會認為斷網(wǎng)這種情況才會是錯誤的,其他情況比如:404,403等請求錯誤都是認為請求成功了,應(yīng)為它發(fā)起請求并收到了響應(yīng)。
所以我們對返回狀態(tài)進行校驗,然后拋出錯誤,以便返回正常的報錯信息。
對fetch中的錯誤進行拋出,然后對不同的狀態(tài)返回不同的報錯信息
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}
const errortext = response.statusText;
const error = new Error(errortext);
error.name = response.status;
error.response = response;
throw error;
}
let fetchChain = fetch(newUrl, newOptions)
.then(checkStatus)
.then(response => {
if (response.status === 204) {
return {};
}
if (newOptions.responseType === 'blob') {
return response.blob();
}
return newOptions.responseType === 'text' ? response.text() : response.json();
});
上面的代碼中的checkStatus 主要是檢查成功狀態(tài)下的問題處理。其他情況不做處理。
如果要對其他的狀態(tài)進行檢查 需要通過catch來進行異常的捕獲
。。。
fetchChain = fetchChain.catch(e => {
const status = e.name;
// 監(jiān)聽到 401 錯誤 重新登陸
if (status === 401) {
// code
}
// 監(jiān)聽到 網(wǎng)絡(luò)請求錯誤
// https://github.com/github/fetch/issues/201
if (status === 'TypeError') {
// code
}
if (status === 501) {
// 后端正常的報錯/服務(wù)器報錯
}
if (status === 403) {
notification.error({
message: `${status}`,
description: '抱歉,您無權(quán)限訪問此功能',
});
return;
}
// 其他錯誤
notification.error({
message: `${status}`,
description: e.message,
});
});
上面代碼就實現(xiàn)了 fetch對某些錯誤http狀態(tài)不會reject,同時面對不同狀態(tài)的不同處理。
關(guān)于catch理解可以看對Promise中的resolve,reject,catch理解
2. fetch不支持abort,不支持超時控制。
fetch不像大多數(shù)ajax庫那樣對請求設(shè)置超時timeout,它沒有有關(guān)請求超時的feature,這一點比較蛋疼。所以在fetch標準添加超時feature之前,都需要polyfill該特性。
實際上,我們真正需要的是abort(), timeout可以通過timeout+abort方式來實現(xiàn),起到真正超時丟棄當前的請求。
而在目前的fetch指導規(guī)范中,fetch并不是一個具體實例,而只是一個方法;其返回的promise實例根據(jù)Promise指導規(guī)范標準是不能abort的,也不能手動改變promise實例的狀態(tài),只能由內(nèi)部來根據(jù)請求結(jié)果來改變promise的狀態(tài)。
既然不能手動控制fetch方法執(zhí)行后返回的promise實例狀態(tài),那么是不是可以創(chuàng)建一個可以手動控制狀態(tài)的新Promise實例呢。所以:
實現(xiàn)fetch的timeout功能,其思想就是新創(chuàng)建一個可以手動控制promise狀態(tài)的實例,根據(jù)不同情況來對新promise實例進行resolve或者reject,從而達到實現(xiàn)timeout的功能;
方法一:單純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);
}
);
});
};
當然在上面基礎(chǔ)上可以模擬類似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實例數(shù)組參數(shù),表示多個promise實例中任何一個最先改變狀態(tài),那么race方法返回的promise實例狀態(tài)就跟著改變,具體可以參考這里。
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])
}
通過上面兩種方式發(fā)現(xiàn)可以發(fā)現(xiàn):
timeout不是請求連接超時的含義,它表示請求的response時間,包括請求的連接、服務(wù)器處理及服務(wù)器響應(yīng)回來的時間;
fetch的timeout即使超時發(fā)生了,本次請求也不會被abort丟棄掉,它在后臺仍然會發(fā)送到服務(wù)器端,只是本次請求的響應(yīng)內(nèi)容被丟棄而已; 這樣就會造成了流量的浪費。 關(guān)于怎么正在取消請求可以參考:https://github.com/hjylewis/trashable
參考文章:
Fetch 手動終止
Fetch的數(shù)據(jù)獲取和發(fā)送以及異常處理
fetch的常見問題及其解決辦法