起因
我在網(wǎng)上看到很多關(guān)于fetch timeout的封裝乒疏,但是我覺得是偽timeout崎岂,只是拋錯逸月,但是fetch的Promise鏈會一直執(zhí)行下去
Promise.race([
fetch('/api')
.then(res => res.json())
.then(res => console.log(555)),
new Promise(function(resolve, reject) {
setTimeout(() => {
reject(new Error('request timeout'));
console.log(111);
}, 100);
})
]);
結(jié)果:
可以看到就算超時后矩屁,fetch請求仍按正常順序執(zhí)行,輸出了555舔琅,超時一般會重新請求等恐,這樣到最后就有可能輸出2次或者多次555,試想如果你在then函數(shù)里面執(zhí)行setState操作备蚓,這樣視圖就會更新2次或者多次课蔬,這樣顯然不是我們想要的結(jié)果,我們想要的是獲取結(jié)果后執(zhí)行一次
針對以上缺點(diǎn)進(jìn)行改進(jìn)
于是我封裝以下代碼星著,支持timeout(我這個其實(shí)也是偽timeout购笆,沒辦法,除非使用xhr虚循,但是超時后Promise鏈只會執(zhí)行報錯,因為結(jié)果和報錯使用同一個Promise)和重新請求样傍,由于返回值是一個Promise横缔,用法和fetch保持一致
支持Promise.all,.race
方法
代碼地址
class TimeoutError extends Error {
constructor(message) {
super(message);
this.name = 'TimeoutError';
}
}
/**
* 提供參數(shù)校驗和wrapper功能
*
* @param {*} url
* @param {*} [options={ method: 'GET' }]
* @returns {Promise} the request result
*/
function request(url, options = { method: 'GET' }) {
let retryCount = 0;
let parseJSON = response => {
return response.json();
};
let checkStatus = response => {
if (response.status >= 200 && response.status < 300) {
return response;
}
let error = new Error(response.statusText);
error.response = response;
throw error;
};
class Request {
constructor(url, { retry, timeout, ...options }) {
this.url = url;
this.retry = retry || 0;
this.timeout = timeout || 10000;
this.options = options;
}
then(fn) {
let done = false;
setTimeout(() => {
// 無論是請求重試還是最終超時錯誤,此次請求得到的結(jié)果作廢
if (retryCount < this.retry && !done) {
done = true;
retryCount++;
this.then(fn);
} else {
let error = new TimeoutError(`timeout of ${this.timeout}ms execeeded`);
this.catchError(error);
}
}, this.timeout);
fetch(this.url, this.options)
.then(checkStatus)
.then(parseJSON)
.then(res => {
// 未進(jìn)入重試或者超時錯誤衫哥,返回結(jié)果
if (!done) {
fn(res);
done = true;
}
})
.catch(err => {
this.catchError(err);
});
return this;
}
catch(fn) {
this.catchError = fn;
}
}
return new Promise((resolve, reject) =>
new Request(url, options).then(res => resolve(res)).catch(err => reject(err))
);
}
request('/api', {
retry: 2,
timeout: 1000
}).then(res => console.log(res))
使用封裝后的fetch進(jìn)行請求
設(shè)置Cache-Control:2s和timeout:1000ms后的請求情況
可以看到1.49s后請求才完全響應(yīng)茎刚,而我們設(shè)置了1s重新請求,所以第二次請求由于上次請求緩存未失效的原因撤逢,在1.49s的時候利用了上次請求作為結(jié)果進(jìn)行了響應(yīng)
設(shè)置緩存膛锭,第一次超時請求結(jié)果作廢(then函數(shù)不執(zhí)行)粮坞,第二次請求直接拿了第一次的緩存,這樣減少了請求響應(yīng)時間還減輕了服務(wù)器的壓力
不設(shè)置緩存初狰,如果網(wǎng)絡(luò)那段時間不太好莫杈,第三次請求才順利拿到結(jié)果,也有可能第二次拿到請求奢入,抑或是重試2次以后還是超時了
請求重試最好跟cache-control配合使用筝闹,這樣當(dāng)前面請求超時結(jié)果作廢后,第二次請求會等到第一次請求結(jié)果的返回腥光,前提是緩存沒有失效
緩存失效時間是從響應(yīng)開始時計算的关顷,一般配合超時重新請求的話,timeout設(shè)置為正常響應(yīng)的1.5倍武福,max-age應(yīng)該設(shè)置為timeout的1.5+倍(或者為timeout的2倍议双,方便利用上次響應(yīng)結(jié)果),具體數(shù)值需要根據(jù)具體情況合理設(shè)置
可能最后會有人有這樣的疑問捉片,你使用緩存聋伦,即上一次請求超時響應(yīng)的結(jié)果,那還不如Promise.race的方法簡單界睁,一樣的效果
使用緩存的優(yōu)勢就是如果第一次超時響應(yīng)的時間短于timeout加正常響應(yīng)甚至又一次超時的時間觉增,而且緩存沒有失效,那么既節(jié)省了時間又節(jié)省了服務(wù)器的壓力翻斟,假如失效了呢逾礁?重新請求唄!不管怎樣访惜,利用緩存絕對是比不利用的好
最后嘹履,如果你覺得這篇文章對你有用的話,麻煩給個小星星债热,如有錯誤的話砾嫉,也歡迎指正