fetch timeout + 緩存瞭解下?

原由

我在網上看到不少關於fetch timeout的封裝,可是我以爲是僞timeout,只是拋錯,可是fetch的Promise鏈會一直執行下去git

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);
  })
]);
複製代碼

結果: github

image.png
能夠看到就算超時後,fetch請求仍按正常順序執行,輸出了555,超時通常會從新請求,這樣到最後就有可能輸出2次或者屢次555,試想若是你在then函數裏面執行setState操做,這樣視圖就會更新2次或者屢次,這樣顯然不是咱們想要的結果,咱們想要的是獲取結果後執行一次

針對以上缺點進行改進

因而我封裝如下代碼,支持timeout(我這個其實也是僞timeout,沒辦法,除非使用xhr,可是超時後Promise鏈只會執行報錯,由於結果和報錯使用同一個Promise)和從新請求,因爲返回值是一個Promise,用法和fetch保持一致 支持Promise.all,.race方法json

代碼地址api

class TimeoutError extends Error {
  constructor(message) {
    super(message);
    this.name = 'TimeoutError';
  }
}

/** * 提供參數校驗和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(() => {
        // 不管是請求重試仍是最終超時錯誤,這次請求獲得的結果做廢
        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 => {
          // 未進入重試或者超時錯誤,返回結果
          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進行請求

設置Cache-Control:2s和timeout:1000ms後的請求狀況 能夠看到1.49s後請求才徹底響應,而咱們設置了1s從新請求,因此第二次請求因爲上次請求緩存未失效的緣由,在1.49s的時候利用了上次請求做爲結果進行了響應 設置緩存,第一次超時請求結果做廢(then函數不執行),第二次請求直接拿了第一次的緩存,這樣減小了請求響應時間還減輕了服務器的壓力 緩存

image.png
image.png


image.png
image.png
不設置緩存,若是網絡那段時間不太好,第三次請求才順利拿到結果,也有可能第二次拿到請求,抑或是重試2次之後仍是超時了
image.png
image.png

請求重試最好跟cache-control配合使用,這樣當前面請求超時結果做廢后,第二次請求會等到第一次請求結果的返回,前提是緩存沒有失效 緩存失效時間是從響應開始時計算的,通常配合超時從新請求的話,timeout設置爲正常響應的1.5倍,max-age應該設置爲timeout的1.5+倍(或者爲timeout的2倍,方便利用上次響應結果),具體數值須要根據具體狀況合理設置服務器

可能最後會有人有這樣的疑問,你使用緩存,即上一次請求超時響應的結果,那還不如Promise.race的方法簡單,同樣的效果 使用緩存的優點就是若是第一次超時響應的時間短於timeout加正常響應甚至又一次超時的時間,並且緩存沒有失效,那麼既節省了時間又節省了服務器的壓力,假如失效了呢?從新請求唄!無論怎樣,利用緩存絕對是比不利用的好網絡

最後,若是你以爲這篇文章對你有用的話,麻煩給個小星星,若有錯誤的話,也歡迎指正app

相關文章
相關標籤/搜索