fetch处理超时

# 背景

原本接口请求用的是fetch,而且fetch不支持超时处理,为了有较小的改动就能够实现超时不得不去自己封装或者找第三方插件解决这个问题,在看了一些开源插件后(如fetch-timeout (opens new window)),自己封装了一个fetch_timeout。

# 超时处理

# 简单

方便用于:内嵌方式;【这种方式只是表面上的取消】

核心是利用建立一个超时的abortPromise和接口请求的fetchPromise传入 Promise.race() 来进行处理,哪个Promise先返回结果则最终输出这个Promise的返回值;

创建abortPromise

let abort = null;
let abortPromise = new Promise((resolve, reject) => {
  abort = () => {
    return reject({
      code: 504,
      message: "请求超时!"
    });
  };
});
1
2
3
4
5
6
7
8
9

创建fetchPromise

let fetchPromise=fetch(url,prama)
1

传入Promise.race,设置一个定时器用于触发abortPromise内部函数abort返回一个Promise结果,然后返回最后输出的Promise为 resultPromise

// 最快出结果的promise 作为结果
let resultPromise = Promise.race([fecthPromise, abortPromise]);
setTimeout(() => {
  abort();
}, timeout);
return resultPromise;
1
2
3
4
5
6

# 超时设置

在使用XMLHttpRequest可以设置请求超时时间,可是转用Fetch后,超时时间设置不见了,在网络不可靠的情况下,超时设置往往很有用

ES6以后Promise 出现解决地狱回调等不优雅的代码风格。个人理解这个更像是一个生产者和消费者的关系,查看 Promise文档,有以下两个方法

  1. Promise.race([promise1,promise2]) 传入多个Promise对象,等待最快对象完成
  2. Promise.all([promise1,promise2]) 传入多个Promise 对象,等待所有对象完成

有了以上知识后,结合函数setTimeout就可以实现超时设置

//ahutor:herbert qq:464884492
let timeoutPromise = (timeout) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("我是 timeoutPromise,已经完成了");
        }, timeout);
    });
}
let requestPromise = (url) => {
    return fetch(url);
};
Promise.race([timeoutPromise(1000), requestPromise("https://www.baidu.com")])
    .then(resp => {
        console.log(resp);
    })
    .catch(error => {
        console.log(error);
    });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 取消请求【终极方案】

将上边的代码拷贝的浏览器控制台并将network设置为Slow3G。运行就会发现,虽然我们在控制台看到了超时信息,但切换到netwok页签中发现请求依然正常进行中,并返回了正确的内容。这并不是我想要的结果,我希望超时时间到了,请求也应该终止。

fetch请求成功后,默认返回一个Response对象,那么我们如何在代码中构造一个这样的对象呢?

  timeoutResp=new Response("timeout", { status: 504, statusText: "timeout " })
  successResp=new Response("ok", { status: 200, statusText: "ok " })
1
2

AbortController 用于手动终止一个或多个DOM请求,通过该对象的AbortSignal注入的Fetch的请求中。所以需要完美实现timeout功能加上这个就对了;

有些朋友在settimeout中通过 reject(new Error('网络超时'))实现。其实这样只是让前端感知当前请求超时了,并没有真正终止本次请求。所以必须借助AbortSignal信号对象。此功能目前还处于试验阶段,使用需谨慎;

let controller = new AbortController();
let signal = controller.signal;

let timeoutPromise = (timeout) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(new Response("timeout", { status: 504, statusText: "timeout " }));
            controller.abort();
        }, timeout);
    });
}
let requestPromise = (url) => {
    return fetch(url, {
        signal: signal
    });
};
Promise.race([timeoutPromise(1000), requestPromise("https://www.baidu.com")])
    .then(resp => {
        console.log(resp);
    })
    .catch(error => {
        console.log(error);
    });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 封装

# 自己缝制

/**
* 实现fetch的timeout 功能
* @param {object} fecthPromise fecth
* @param {Number} timeout 超时设置,默认5000ms
* */
function fetch_timeout(fecthPromise, timeout = 5000) {
  let abort = null;
  let abortPromise = new Promise((resolve, reject) => {
    abort = () => {
      return reject({
        code: 504,
        message: "请求超时!"
      });
    };
  });
 
  // 最快出结果的promise 作为结果
  let resultPromise = Promise.race([fecthPromise, abortPromise]);
  setTimeout(() => {
    abort();
  }, timeout);
 
  returnresultPromise.then(res => {
      clearTimeout(timeout);
      return res;
   });

}
 
export default fetch_timeout;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# fetch-timeout库实现

let fetchPromise;

if (typeof window === 'undefined' || window.hasOwnProperty('fetch')) {
  fetchPromise = require('node-fetch'); // eslint-disable-line global-require
} else {
  fetchPromise = fetch;
}

function timeoutPromise(promise, timeout, error) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(error);
    }, timeout);
    promise.then(resolve, reject);
  });
}

module.exports = function fetchTimeout(url, options, timeout, error) {
  error = error || 'Timeout error';
  options = options || {};
  timeout = timeout || 10000;
  return timeoutPromise(fetchPromise(url, options), timeout, error);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# Axios封装取消处理

想见【Axios分析】取消请求

上次更新: 2022/04/15, 05:41:27
×