当前位置:   article > 正文

手写VUE后台管理系统10 - 封装Axios实现异常统一处理_封装axios统一处理错误

封装axios统一处理错误


在这里插入图片描述

axios 是一个易用、简洁且高效的http库
axios 中文文档:http://www.axios-js.com/zh-cn/docs/


前后端交互约定

在本项目中,前后端交互统一使用 application/json;charset=UTF-8请求方式,后端返回对象统一为如下格式

export interface ResponseBody<T = any> {
    status: boolean,	// 业务处理状态,true表示正常,false表示异常
    code: string		// 业务处理状态码
    message: string,	// 提示信息
    data?: T			// 业务处理返回数据
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

安装

yarn add axios
  • 1

创建Axios实例

src 目录下创建 http 目录,http 请求相关的文件都放置于该目录

创建 axios.ts 文件,用于定义 axios

// axios.ts
const instance: AxiosInstance = axios.create({
    baseURL: import.meta.env.VITE_APP_BASE_API,
    timeout: 60000,
    headers: { 'Content-Type': 'application/json;charset=UTF-8' },
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

其中,import.meta.env.VITE_APP_BASE_API.env 中配置的环境变量,不同环境使用不同的请求地址

拦截器

通过 instance.interceptors.request.use 实现前置拦截器,发起请求前执行,用于对请求对象进行加工处理。

下面这段代码主要做的事情就是将 token 设置到请求头中。

// axios.ts
async function requestHandler(config: InternalAxiosRequestConfig & RequestConfigExtra): Promise<InternalAxiosRequestConfig> {
    if (config.modulePrefix) {
        config.url = config.modulePrefix + config.url
    }
    const token = useAuthorization()
    if (token.value && config.token !== false) {
        config.headers.set("Authorization", token.value)
    }
    console.log("execute http request:" + config.url)
    return config
}

instance.interceptors.request.use(requestHandler)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

RequestConfigExtra 为自定义参数,在本项目中定义如下,可根据需要自行扩展。

export interface RequestConfigExtra {
    // 模块前缀
    modulePrefix?: string,
    // 发起请求时,是否需要在请求头中附加 token
    token?: boolean,
    // 成功处理函数,默认为 false,如果为 false,则什么都不做,如果为 true,则自动提示成功信息,如果为 Function,则自定义处理结果
    success?: boolean | ((response: ResponseBody<any>) => void),
    // 失败处理函数,默认为 true,如果为 false,则什么都不做,如果为 true,则自动提示失败信息,如果为 Function,则自定义处理结果
    error?: boolean | ((response: ResponseBody<any>) => void),
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

通过 instance.interceptors.response.use 实现后置拦截器,对后端返回数据进行处理。

function responseHandler(response: any): ResponseBody<any> | AxiosResponse<any> | Promise<any> | any {
    return response.data
}

function errorHandler(errorInfo: AxiosError): Promise<any> {
    if (errorInfo.response) {
        const { data, status, statusText } = errorInfo.response as AxiosResponse<ResponseBody>
        if (status === 401) {
            const token = useAuthorization()
            token.value = null
            message.error(data?.message || statusText)
            router.push({path: '/login', query: { 
                redirect: router.currentRoute.value.fullPath
            }})
        } else {
            message.error(data?.message || statusText)
        } 
    }
    return Promise.reject(errorInfo)
}

instance.interceptors.response.use(responseHandler, errorHandler)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

errorHandler 方法对系统异常进行了统一处理,如果后端返回的 status 不是 200,则会执行该处理方法,如果返回 401,表示没有通过登录鉴权,自动跳转登录页,如果是其它异常码,则提示错误信息。

封装请求方法

这里对 restful 常用的四种请求方式进行进一步的封装

// axios.ts
function instancePromise<R = any, T = any>(options: AxiosRequestConfig<T> & RequestConfigExtra): Promise<ResponseBody<R>> {
    return new Promise((resolve, reject) => {
        instance.request<any, ResponseBody<R>>(options)
            .then((res) => {
                try {
                    resolve(responseBodyHandle(res, options))
                } catch (err) {
                    reject(err || new Error('response handle error!'))
                }
            })
            .catch((e: Error | AxiosError) => {
                reject(e)
            })
    })
}

export function doGet<R = any, T = any>(url: string, params?: T, config?: AxiosRequestConfig & RequestConfigExtra): Promise<ResponseBody<R>> {
    const options = {
        url,
        params,
        method: RequestEnum.GET,
        ...config,
    }
    return instancePromise<R, T>(options)
}

export function doPost<R = any, T = any>(url: string, data?: T, config?: AxiosRequestConfig & RequestConfigExtra): Promise<ResponseBody<R>> {
    const options = {
        url,
        data,
        method: RequestEnum.POST,
        ...config,
    }
    return instancePromise<R, T>(options)
}

export function doPut<R = any, T = any>(url: string, data?: T, config?: AxiosRequestConfig & RequestConfigExtra): Promise<ResponseBody<R>> {
    const options = {
        url,
        data,
        method: RequestEnum.PUT,
        ...config,
    }
    return instancePromise<R, T>(options)
}

export function doDelete<R = any, T = any>(url: string, data?: T, config?: AxiosRequestConfig & RequestConfigExtra): Promise<ResponseBody<R>> {
    const options = {
        url,
        data,
        method: RequestEnum.DELETE,
        ...config,
    }
    return instancePromise<R, T>(options)
}
  • 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

业务异常处理

在拦截器中,我们对系统异常进行了统一处理,在实际项目中,更多的情况是前端请求没有通过后端的业务校验,后端返回错误信息,前端进行提示。

创建一个业务异常类

export class ResponseBodyError extends Error {
    code: string
    message: string
    cause: any

    constructor({ code, message, cause }: { code: string, message: string, cause?: any }) {
        super();
        this.code = code;
        this.message = message;
        this.cause = cause
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

实现业务异常统一处理,通过在请求时定义 RequestConfigExtra 中的参数来实现对后端返回结果的提示。

比如:查询请求,设置为 {success:false, error: true},如果请求失败,提示错误信息,如果请求成功,不作处理。业务请求,设置为 {success: true, error: true},如果请求失败,提示错误信息,如果处理成功,提示操作成功。

function responseBodyHandle<R = any, T = any>(response: ResponseBody<R>, options: AxiosRequestConfig<T> & RequestConfigExtra): any {
    const { status, message:msg, code, data } = response
    const { success, error } = options
    if (status === true) {
        if (success === true) {
            message.success(msg ?? "操作成功")
        } else if (isFunction(success)) {
            success(response)
        }
        return response
    } else {
        if (isFunction(error)) {
            error(response)
        } else if (error !== false) {
            message.error(msg ?? "操作失败")
        }
        throw new ResponseBodyError({ code, msg })
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

完整代码如下:

// axios.ts
/**
 * 创建 Axios 实例
 */
const instance: AxiosInstance = axios.create({
    baseURL: import.meta.env.VITE_APP_BASE_API,
    timeout: 60000,
    headers: { 'Content-Type': 'application/json;charset=UTF-8' },
})

/**
 * 前置拦截器
 */
async function requestHandler(config: InternalAxiosRequestConfig & RequestConfigExtra): Promise<InternalAxiosRequestConfig> {
    if (config.modulePrefix) {
        config.url = config.modulePrefix + config.url
    }
    const token = useAuthorization()
    if (token.value && config.token !== false) {
        config.headers.set(authorizationHeader, authorizationValue())
    }
    console.log("execute http request:" + config.url)
    return config
}

/**
 * 后置拦截器
 */
function responseHandler(response: any): ResponseBody<any> | AxiosResponse<any> | Promise<any> | any {
    return response.data
}

/**
 * 系统异常统一处理函数
 */
function errorHandler(errorInfo: AxiosError): Promise<any> {
    if (errorInfo.response) {
        const { data, status, statusText } = errorInfo.response as AxiosResponse<ResponseBody>
        if (status === 401) {
            const token = useAuthorization()
            token.value = null
            message.error(data?.message || statusText)
            router.push({path: '/login', query: { 
                redirect: router.currentRoute.value.fullPath
            }})
        } else {
            message.error(data?.message || statusText)
        } 
    }
    return Promise.reject(errorInfo)
}

instance.interceptors.request.use(requestHandler)
instance.interceptors.response.use(responseHandler, errorHandler)

/**
 * 业务异常统一处理函数
 */
function responseBodyHandle<R = any, T = any>(response: ResponseBody<R>, options: AxiosRequestConfig<T> & RequestConfigExtra): any {
    const { status, message:msg, code, data } = response
    const { success, error } = options
    if (status === true) {
        if (success === true) {
            message.success(msg ?? "操作成功")
        } else if (isFunction(success)) {
            success(response)
        }
        return response
    } else {
        if (isFunction(error)) {
            error(response)
        } else if (error !== false) {
            message.error(msg ?? "操作失败")
        }
        throw new ResponseBodyError({ code, msg })
    }
}

function instancePromise<R = any, T = any>(options: AxiosRequestConfig<T> & RequestConfigExtra): Promise<ResponseBody<R>> {
    return new Promise((resolve, reject) => {
        instance.request<any, ResponseBody<R>>(options)
            .then((res) => {
                try {
                    resolve(responseBodyHandle(res, options))
                } catch (err) {
                    reject(err || new Error('response handle error!'))
                }
            })
            .catch((e: Error | AxiosError) => {
                reject(e)
            })
    })
}

export function doGet<R = any, T = any>(url: string, params?: T, config?: AxiosRequestConfig & RequestConfigExtra): Promise<ResponseBody<R>> {
    const options = {
        url,
        params,
        method: RequestEnum.GET,
        ...config,
    }
    return instancePromise<R, T>(options)
}

export function doPost<R = any, T = any>(url: string, data?: T, config?: AxiosRequestConfig & RequestConfigExtra): Promise<ResponseBody<R>> {
    const options = {
        url,
        data,
        method: RequestEnum.POST,
        ...config,
    }
    return instancePromise<R, T>(options)
}

export function doPut<R = any, T = any>(url: string, data?: T, config?: AxiosRequestConfig & RequestConfigExtra): Promise<ResponseBody<R>> {
    const options = {
        url,
        data,
        method: RequestEnum.PUT,
        ...config,
    }
    return instancePromise<R, T>(options)
}

export function doDelete<R = any, T = any>(url: string, data?: T, config?: AxiosRequestConfig & RequestConfigExtra): Promise<ResponseBody<R>> {
    const options = {
        url,
        data,
        method: RequestEnum.DELETE,
        ...config,
    }
    return instancePromise<R, T>(options)
}
  • 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
  • 130
  • 131
  • 132
  • 133
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/article/detail/57046
推荐阅读
相关标签
  

闽ICP备14008679号