Axios 常见用法

Axios | 基本用法 | 配置修改 | 响应封装 | 请求取消 | 请求重试 | 文件下载

Posted by luoruiqing on October 13, 2020

Axios

Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中

基本使用

创建实例

1
2
3
4
5
6
7
import axios from 'axios'
// 创建实例
const service = axios.create({
    baseURL: 'https://some-domain.com/api/', // 默认URL
    timeout: 1000, // 超时时间
    headers: { 'X-Custom-Header': 'foobar' } // 默认的请求头
})

这里推荐创建 实例, 这样可以有 差别化 配置项的请求客户端

修改配置

1
2
3
4
5
6
7
8
9
import axios from 'axios'

const service = axios.create()
// >>>>>> 全局形式, 这样的修改将应用到整个站点的axios客户端内 <<<<<<
axios.defaults.baseURL = 'https://api.example.com'; // 所有AXIOS客户端对应请求的域名
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN; // 所有类型的请求添加TOKEN
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'; // 给POST类型请求单独增加头
// >>>>>> 实例形式, 如果考虑长期维护建议使用这种方式: <<<<<<
service.defaults.baseURL = 'https://api.example.com'; // 当前实例客户端对应的请求域名

状态码

默认 200300 的响应状态码对应 Promise.resolve, 超出范围则 Promise.rejecte

可以传入回调方法来决定自己的调用状态, 若返回值为 true , null , undefinedresolve 否则 rejecte

1
2
3
4
const service = axios.create({
    validateStatus: status => status >= 200 && status < 300
})
// 或 validateStatus 加入到请求拦截器内

取消请求

1
2
3
4
5
6
7
// 简单的取消例子
let cancel;
axios.get('/user/12345', {
  cancelToken: new axios.CancelToken(func => cancel = func)
});
// 取消请求
cancel && cancel();

注意: 可以使用同一个 cancel token 取消多个请求

高级

数据处理

这里可以用于表单类型数据提交格式的转化

1
2
3
4
5
6
7
8
9
10
11
12
const service = axios.create({
    // `transformRequest` 允许在向服务器发送前,修改请求数据
    // 只能用在 'PUT', 'POST' 和 'PATCH' 这几个请求方法
    // 后面数组中的函数必须返回一个字符串,或 ArrayBuffer,或 Stream
    transformRequest: [function (data, headers) { 
        // 例如from-data格式化
        // data = Qs.stringify(data, { arrayFormat: 'repeat' }) 
        return data 
    }],
    // `transformResponse` 在传递给 then/catch 前,允许修改响应数据
    transformResponse: [function (data) { return data }],
})

下载进度

1
2
3
4
5
6
let percent, loaded, total;
axios.get("/demo/file.xlsx", data, {
    onDownloadProgress(e) {
        [loaded, total, percent] = [e.loaded, e.total, parseInt(100 * (e.loaded / e.total))] // 已下载/总大小/百分比
    }
})

封装

响应封装

常见的错误处理及相应处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { Message, MessageBox } from 'element-ui' // 以 element-ui 为例

/* 每次响应后执行 */
service.interceptors.response.use(
    response => {
        // 根据 **validateStatus** 的默认配置, 只有200到300之间的状态码能执行这个方法, 固不判断
        if (response.headers['content-type'] === "application/json") return response.data // 响应为JSON时, 只返回数据
        return response // 其他类型响应返回原始对象
    },
    error => {
        // 这里假设400错误的数据结构 {"message": "XXXX错误"}
        if (error.response.status === 400) Message.error(error.response.data.message) // 立刻弹出错误信息
        else if (error.response.status === 401) MessageBox.alert('您的登录信息已失效, 请重新登录!', '提示', { callback: () => logout() })
        else Message.error(`服务器错误!\n${(error.response || {}).data || String(error)}`) // 立刻弹出错误信息
        Promise.reject(error)
    }
)

请求重试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import axios from 'axios'

// 创建实例
const service = axios.create({ timeout: 5000 })

// 重试配置
const shouldRetry = (error) => true // 重试条件 
axios.default.retry = 3 // 重试次数
axios.default.retryDelay = 2000 // 重试延时
axios.default.shouldRetry = shouldRetry

// 返回错误时拦截,并尝试重试
AxiosInst.interceptors.response.use(undefined, function (error) {
    const config = (error.config || { __retryCount: 0 , shouldRetry}) // 默认值兼容
    if (!config || !config.retry || config.__retryCount >= config.retry || config.shouldRetry(error)) return Promise.reject(error);
    console.log(config.url + `自动重试第 ${config.__retryCount += 1} 次`) // 打印并增加重试次数
    return new Promise(resolve => setTimeout(() => resolve(), config.retryDelay || 1)).then(() => 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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import axios from 'axios'

// 创建实例
const service = axios.create({ timeout: 5000 })
// 通用配置
axios.default.withCredentials = true // 跨域是否携带cookie


// 请求拦截器
service.interceptors.request.use(
    /* 每次请求前执行, 准备的工作 */
    config => {
        config.headers = {
            'access-token': '<携带你的TOKEN>', // 常见的携带token
        }
        return config
    }, // 请求前配置修改
    error => Promise.reject(error)
)
// 响应拦截器
service.interceptors.response.use(
    /* 每次响应后执行 */
    response => {
        // 重试的实现逻辑
        // TODO
        // 文件下载的处理
        if (response.request.responseType === 'blob' && response.status === 200) { // 下载文件
            // 获取文件名
            const filename = (response.headers['content-disposition'] || '')
                .split('; ').reverse()[0].replace('filename=', '') || '未知文件格式'
            // 下载方法
            const link = document.createElement('a')
            link.style.display = 'none'
            link.href = window.URL.createObjectURL(new Blob([response.data]))
            link.setAttribute('download', filename)
            document.body.appendChild(link)
            link.click()
            setTimeout(() => document.body.removeChild(link), 1000) // 清除文件按钮
            return response // 文件下载返回原本的响应对象
        } else { // 非文件(二进制流)响应
            if (response.status !== 200) { // 非成功的响应
                /* 无权限则清除登录信息, 跳转登录页 */
                if (response.status === 401) window.location.href = '/login'
                /* >>>>>>  更多的状态码/响应头/内容来决定的逻辑 <<<<<< */
                /* 弹窗警告, 并推出错误到调用层 */
                return Promise.reject(new Error(response.data.message || 'Error'))
            }
            // 响应成功, 则直接返回数据, 无需关心其他属性
            return response.data
        }
    },
    error => Promise.reject(error)
)

export default service

查阅