axios 的错误处理
PHP / 4 月前

axios 的错误处理

Axios 是目前使用最为广泛的 http 请求工具包,在进行错误处理时,基于框架提供的拦截器,我们可以快速的实现错误处理。

axios interceptor

axios.interceptors.response.use(function (response) {
    return response;
}, function (error) {
    // 如4xx/5xx等基本错误的处理
    alert('全局错误处理')

    return Promise.reject(error); 
});

但 axios 提供的 response 拦截器是全局的,若我们想对某个具体请求进行错误处理时,情况就稍微有点复杂了。

如有一个投票接口,由于后端限制了投票次数,当投票超限时我们需要单独处理;返回的响应如下:

HTTP/1.0 429 Too Many Requests

{"error_code":4291011,"message":"今日投票次数超限"}

可能对于部分前端同学来说,处理方式是直接在 response 拦截器加上相应的条件判断就好了:

axios.interceptors.response.use(function (response) {
    return response;
}, function (error) {
    if (error.response.status == 429) {
        if (error.response.data.error_code == 4291011) {
            // 单独处理投票错误
        } else if (error.response.data.error_code == 4291011) {
				    // 作品票数异常,需先通过滑动验证码
				}
    }

    // 处理其他 如4xx/5xx等基本错误的处理
    return Promise.reject(error); 
});

但是这样做却存在很多问题

  • 随着各种错误码的增多,拦截器需要处理的情况越来越多,最终充满着大量的 if-else
  • 具体的错误码应该和具体发起请求的代码放在一起,一来方便查看,二来好扩展及定位

所以我们可以把错误处理逻辑移到调用代码处,如下:

axios.post('/vote/1').then(function (response) {
    // success
}).catch(function (error) {
    let code = error.response.data.error_code
    if (code == 4291011) {
        alert('投票超限')
    } elseif (code == 4030001) {
        alert('作品票数异常,需先通过滑动验证码')
    }
});

但这样又存在一个问题,由于 axios 拦截器的代码会比 catch 先执行,所以当执行到 catch 时,实际上 response 拦截器的代码已全部执行完成,所以会先后弹出「全局错误处理」-> 「投票超限」。

这显然不是我们想要的,我们希望当我们单独处理错误后,先执行具体的业务错误处理,最后在执行全局的错误处理。

如下:

  • 针对这种没有 catch 的情况,当请求错误后,我们希望由全局错误进行处理。
axios.post('/vote/1').then(function (response) {
    // success
})

这默认是可行的,不需要做额外的准备工作。

  • 当我们使用 catch 后,我们希望应该先处理自定义的错误,最后在处理全局错误。
axios.post('/vote/1').then(function (response) {
    // success
}).catch(error => {
    // custom error 
})

然而目前这样是行不通的,看了 axios request 方法源码后得知,框架在发起请求时,并没有给我提供相应的钩子;所以在 Promise 执行到 catch 时,拦截器里的代码一定已经执行过了。

axios config

我们只能依赖 axios 提供的 config 来完成这个特性,如下所示:

axios.interceptors.response.use(function (response) {
    return response;
}, function (error) {
    error.globalErrorProcess = function () {
        switch (this.response.status) {
            case 401: // 处理基本 401 错误
                break;
            case 404: // 处理基本 404 错误
                break;
            case 403: // 处理基本 403 错误
                break;
                      // 处理其他4xx/5xx等基本错误的处理
        }

        return Promise.reject(this);
    };

    if(error.config.hasOwnProperty('catch') && error.config.catch == true) {
        return Promise.reject(error);
    }

    return error.globalErrorProcess()
});

我们定义一个全局的错误处理器,并把他赋给 error 对象的 globalErrorProcess 方法。接着判断当前请求 config 是否启用 catch,若启用,默认不进行任何错误处理,交由调用方自行负责;否则用全局错误处理。

在使用时,若需要自定义捕获错误,可显示传递一个 config,相应请求方法的 API 如下:

  • axios.request(config)
  • axios.get(url[, config])
  • axios.delete(url[, config])
  • axios.head(url[, config])
  • axios.options(url[, config])
  • axios.post(url[, data[, config]])
  • axios.put(url[, data[, config]])
  • axios.patch(url[, data[, config]])
axios.post('https://api.github.com/xxx', null, {catch: true}).then(function (response) {
    console.log(response);
}).catch(function (error) {
    let code = error.response.data.error_code

    if (code == 4291011) {
        // 今日投票次数太多,显示关注公众号二维码
    } else if (code == 4031011) {
        // 不允许的投票时间段,
    } else if (code == 4291012) {
        // 作品票数异常,需先通过滑动验证码
    }

    return error.globalErrorProcesser()
});

最后别忘了显示的调用全局错误处理,否则是不会懒觉到其他异常处理的。

如果你有更好的方案,欢迎留言一起探讨。

参考 & 讨论


Godruoyi