V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
ssttm169

axios 请求超时,设置重新请求的完美解决方法

  •  
  •   ssttm169 ·
    ssttm169 · Jun 5, 2018 · 3645 views
    This topic created in 2885 days ago, the information mentioned may be changed or developed.

    自从使用 Vue2 之后,就使用官方推荐的 axios 的插件来调用 API,在使用过程中,如果服务器或者网络不稳定掉包了, 你们该如何处理呢? 下面我给你们分享一下我的经历。

    具体原因

    最近公司在做一个项目, 服务端数据接口用的是 Php 输出的 API, 有时候在调用的过程中会失败, 在谷歌浏览器里边显示 Provisional headers are shown。

    按照搜索引擎给出来的解决方案,解决不了我的问题.


    最近在研究 AOP 这个开发编程的概念,axios 开发说明里边提到的栏截器(axios.Interceptors)应该是这种机制,降低代码耦合度,提高程序的可重用性,同时提高了开发的效率。


    带坑的解决方案一

    我的经验有限,觉得唯一能做的,就是 axios 请求超时之后做一个重新请求。通过研究 axios 的使用说明,给它设置一个 timeout = 6000

    axios.defaults.timeout =  6000;
    

    然后加一个栏截器.

    // Add a request interceptor
    axios.interceptors.request.use(function (config) {
        // Do something before request is sent
        return config;
      }, function (error) {
        // Do something with request error
        return Promise.reject(error);
    });
    
    // Add a response interceptor
    axios.interceptors.response.use(function (response) {
        // Do something with response data
        return response;
      }, function (error) {
        // Do something with response error
        return Promise.reject(error);
    });
    

    这个栏截器作用是 如果在请求超时之后,栏截器可以捕抓到信息,然后再进行下一步操作,也就是我想要用 重新请求。

    这里是相关的页面数据请求。

    this.$axios.get(url, {params:{load:'noload'}}).then(function (response) {
    	//dosomething();
    }).catch(error => {
    	//超时之后在这里捕抓错误信息.
    	if (error.response) {
    		console.log('error.response')
    		console.log(error.response);
    	} else if (error.request) {
    		console.log(error.request)
    		console.log('error.request')
    		if(error.request.readyState == 4 && error.request.status == 0){
    			//我在这里重新请求
    		}
    	} else {
    		console.log('Error', error.message);
    	}
    	console.log(error.config);
    });
    

    超时之后, 报出 Uncaught (in promise) Error: timeout of xxx ms exceeded 的错误。

    在 catch 那里,它返回的是 error.request 错误,所以就在这里做 retry 的功能, 经过测试是可以实现重新请求的功功能, 虽然能够实现 超时重新请求的功能,但很麻烦,需要每一个请 API 的页面里边要设置重新请求。

    看上面,我这个项目有几十个.vue 文件,如果每个页面都要去设置超时重新请求的功能,那我要疯掉的.

    而且这个机制还有一个严重的 bug,就是被请求的链接失效或其他原因造成无法正常访问的时候,这个机制失效了,它不会等待我设定的 6 秒,而且一直在刷,一秒种请求几十次,很容易就把服务器搞垮了,请看下图, 一眨眼的功能,它就请求了 146 次。

    带坑的解决方案二

    研究了 axios 的源代码,超时后, 会在拦截器那里 axios.interceptors.response 捕抓到错误信息, 且 error.code = "ECONNABORTED",具体链接

    https://github.com/axios/axios/blob/26b06391f831ef98606ec0ed406d2be1742e9850/lib/adapters/xhr.js#L95-L101

        // Handle timeout
        request.ontimeout = function handleTimeout() {
          reject(createError('timeout of ' + config.timeout + 'ms exceeded', config, 'ECONNABORTED',
            request));
    
          // Clean up request
          request = null;
        };
    

    所以,我的全局超时重新获取的解决方案这样的。

    axios.interceptors.response.use(function(response){
    ....
    }, function(error){
    	var originalRequest = error.config;
    	if(error.code == 'ECONNABORTED' && error.message.indexOf('timeout')!=-1 && !originalRequest._retry){
    			originalRequest._retry = true
    			return axios.request(originalRequest);
    	}
    });
    

    这个方法,也可以实现得新请求,但有两个问题,1 是它只重新请求 1 次,如果再超时的话,它就停止了,不会再请求。第 2 个问题是,我在每个有数据请求的页面那里,做了许多操作,比如 this.$axios.get(url).then 之后操作。

    完美的解决方法

    以 AOP 编程方式,我需要的是一个 超时重新请求的全局功能, 要在 axios.Interceptors 下功夫,在 github 的 axios 的 issue 找了别人的一些解决方法,终于找到了一个完美解决方案,就是下面这个。

    https://github.com/axios/axios/issues/164#issuecomment-327837467

    //在 main.js 设置全局的请求次数,请求的间隙
    axios.defaults.retry = 4;
    axios.defaults.retryDelay = 1000;
    
    axios.interceptors.response.use(undefined, function axiosRetryInterceptor(err) {
        var config = err.config;
        // If config does not exist or the retry option is not set, reject
        if(!config || !config.retry) return Promise.reject(err);
        
        // Set the variable for keeping track of the retry count
        config.__retryCount = config.__retryCount || 0;
        
        // Check if we've maxed out the total number of retries
        if(config.__retryCount >= config.retry) {
            // Reject with the error
            return Promise.reject(err);
        }
        
        // Increase the retry count
        config.__retryCount += 1;
        
        // Create new promise to handle exponential backoff
        var backoff = new Promise(function(resolve) {
            setTimeout(function() {
                resolve();
            }, config.retryDelay || 1);
        });
        
        // Return the promise in which recalls axios to retry the request
        return backoff.then(function() {
            return axios(config);
        });
    });
    

    其他的那个几十个.vue 页面的 this.$axios 的 get 和 post 的方法根本就不需要去修改它们的代码。

    在这个过程中,谢谢 jooger 给予大量的技术支持,这是他的个人信息 https://github.com/jo0ger, 谢谢。

    以下是我做的一个试验。。把 axios.defaults.retryDelay = 500, 请求 www.facebook.com

    如有更好的建议,请告诉我,谢谢。

    github 源代码, 给星星的人都很美。

    3 replies    2018-06-05 10:33:24 +08:00
    yangg
        1
    yangg  
       Jun 5, 2018
    很赞。
    imdoge
        2
    imdoge  
       Jun 5, 2018
    不错,很有启发,收藏了
    469054193
        3
    469054193  
       Jun 5, 2018
    收藏了
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   5967 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 53ms · UTC 02:22 · PVG 10:22 · LAX 19:22 · JFK 22:22
    ♥ Do have faith in what you're doing.