axios请求封装

2022/1/30 axios

# axios的cancelToken

原文出处: https://segmentfault.com/a/1190000021290514 (opens new window)

  • 通过axios.CancelToken.source生成取消令牌token和取消方法cancel
const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios.get('/user/12345', {
  cancelToken: source.token
}).catch(function(thrown) {
  if (axios.isCancel(thrown)) {
    console.log('Request canceled', thrown.message);
  } else {
    // handle error
  }
});

axios.post('/user/12345', {
  name: 'new name'
}, {
  cancelToken: source.token
})

// cancel the request (the message parameter is optional)
source.cancel('Operation canceled by the user.');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  • 通过axios.CancelToken构造函数生成取消函数
const CancelToken = axios.CancelToken;
let cancel;

axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) {
    // An executor function receives a cancel function as a parameter
    cancel = c;
  })
});

// cancel the request
cancel();
1
2
3
4
5
6
7
8
9
10
11
12
  • 封装取消请求逻辑
// 声明一个 Map 用于存储每个请求的标识 和 取消函数
const pending = new Map()
/**
 * 添加请求
 * @param {Object} config
 */
const addPending = (config) => {
  const url = [
    config.method,
    config.url,
    qs.stringify(config.params),
    qs.stringify(config.data)
  ].join('&')
  config.cancelToken = config.cancelToken || new axios.CancelToken(cancel => {
    if (!pending.has(url)) { // 如果 pending 中不存在当前请求,则添加进去
      pending.set(url, cancel)
    }
  })
}
/**
 * 移除请求
 * @param {Object} config
 */
const removePending = (config) => {
  const url = [
    config.method,
    config.url,
    qs.stringify(config.params),
    qs.stringify(config.data)
  ].join('&')
  if (pending.has(url)) { // 如果在 pending 中存在当前请求标识,需要取消当前请求,并且移除
    const cancel = pending.get(url)
    cancel(url)
    pending.delete(url)
  }
}
/**
 * 清空 pending 中的请求(在路由跳转时调用)
 */
export const clearPending = () => {
  for (const [url, cancel] of pending) {
    cancel(url)
  }
  pending.clear()
}
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
  • 在 axios 拦截器中使用
axios.interceptors.request.use(config => {
  removePending(options) // 在请求开始前,对之前的请求做检查取消操作
  addPending(options) // 将当前请求添加到 pending 中
  // other code before request
  return config
}, error => {
  return Promise.reject(error)
})

axios.interceptors.response.use(response => {
  removePending(response) // 在请求结束后,移除本次请求
  return response
}, error => {
  if (axios.isCancel(error)) {
    console.log('repeated request: ' + error.message)
  } else {
    // handle error code
  }
  return Promise.reject(error)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# axios超时重发

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

  • axios 的超时是在 response 中处理的,所以要在 response 中添加拦截器:
const RETRY_TIMEOUTS = [1, 3, 5, 10]; // seconds
const delay = RETRY_TIMEOUTS[config.retryCount] * 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 || delay);
    });

    // Return the promise in which recalls axios to retry the request
    return backoff.then(function() {
        return axios(config);
    });
});
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
31
32
  • 使用
axios.get('/some/endpoint', { retry: 5, retryDelay: 1000 })
    .then(function(res) {
        console.log('success', res.data);
    })
    .catch(function(err) {
        console.log('failed', err);
    });
1
2
3
4
5
6
7
  • 重新请求1次
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(originalRequest);
	}
});
1
2
3
4
5
6
7
8
9

# axios配置

// 封装axios请求
import axios from "axios"
import qs from "qs"
import router from "../../router"
// 声明一个 Map 用于存储每个请求的标识 和 取消函数
const pending = new Map()
/**
 * 添加请求
 * @param {Object} config
 */
const addPending = (config) => {
	const url = [
		config.method,
		config.url,
		qs.stringify(config.params),
		qs.stringify(config.data),
	].join("&")
	config.cancelToken =
		config.cancelToken ||
		new axios.CancelToken((cancel) => {
			if (!pending.has(url)) {
				// 如果 pending 中不存在当前请求,则添加进去
				pending.set(url, cancel)
			}
		})
}
/**
 * 移除请求
 * @param {Object} config
 */
const removePending = (config) => {
	const url = [
		config.method,
		config.url,
		qs.stringify(config.params),
		qs.stringify(config.data),
	].join("&")
	if (pending.has(url)) {
		// 如果在 pending 中存在当前请求标识,需要取消当前请求,并且移除
		const cancel = pending.get(url)
		cancel(url)
		pending.delete(url)
	}
}
/**
 * 清空 pending 中的请求(在路由跳转时调用)
 * @param {Object} config
 */
export const clearPending = () => {
	for (const [url, cancel] of pending) {
		cancel(url)
	}
	pending.clear()
}

const request = axios.create({
	timeout: 10000,
	withCredentials: false,
})

const CONFIG = {
	retry: 3,
	retryDelay: 1000,
}

// 添加请求拦截器
request.interceptors.request.use(
	(config) => {
		removePending(config)
		addPending(config)
		return config
	},
	(error) => {
		return Promise.reject(error)
	},
)

// 添加响应拦截器
request.interceptors.response.use(
	(response) => {
		// 请求返回结果,移除pending
		removePending(response.config)
		if (
			Number(response.data.stat) === 1 ||
			(response.data.result && Number(response.data.result.status) === 1)
		) {
			return Promise.resolve(response.data)
		} else {
			// 未登录
			if (response.data.stat == 9) {
				router.push({ name: "home" })
			}
			return Promise.reject(response)
		}
	},
	(error) => {
		// retry request: https://github.com/axios/axios/issues/164#issuecomment-327837467
		let config = error.config

		// If config does not exist or the retry option is not set, reject
		if (!config || !CONFIG.retry) return Promise.reject(error)

		// 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(error)
		}

		// Increase the retry count
		config.__retryCount += 1

		// Create new promise to handle exponential backoff
		let backoff = new Promise(function (resolve) {
			setTimeout(function () {
				resolve()
			}, CONFIG.retryDelay)
		})

		// Return the promise in which recalls axios to retry the request
		return backoff.then(function () {
			return request(config)
		})
	},
)

export default request
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129