详解axios是如何处理异常的

 更新时间:2024年05月20日 08:42:54   作者:zhangbao90s  
本文我们将讨论 axios 中是如何处理异常的,在此之前,我们先了解以下 axios 中各种类型的异常,文中通过代码示例讲解的非常详细,具有一定的参考价值,需要的朋友可以参考下

axios 中的正常请求

axios 中当请求服务正常返回时,会落入 .then() 方法中。

axios.get('https://httpstat.us/200')
  .then(res => {
    console.log(res)
  })

效果如下:

axios 会把响应结果包装在返回的 Response 对象的 data 属性中,除此之外:

  • config:即请求配置
  • headers:响应头数据(AxiosHeaders 对象)
  • request:请求实例。浏览器环境就是 XMLHttpRequest 对象
  • status:HTTP 状态码。本案例是 200,表示请求成功处理了
  • statusText: 状态码的文字说明

axios 中的异常请求

axios 中的异常请求分 2 类:有响应异常请求和无响应的异常请求。

有响应的异常

当返回的 HTTP 状态码是 2xx 之外的是狗,就会进入 axios 的 .catch() 方法中。

403 响应:

axios.get('https://httpstat.us/403')
  .catch(err => {
    console.log(err)
  })

效果:

500 响应:

axios.get('https://httpstat.us/500')
  .catch(err => {
    console.log(err)
  })

效果:

以上 2 个场景,返回的都是一个 AxiosError 对象,它继承自 Error。相比 Error,AxiosError 除了常规的 code、message、name 和 stack 属性(非标准)外,还包含 config、request 和 reponse:

  • response 就是响应对象,与正常请求时返回的响应对象完全一致
  • config 和 request 与 Response 对象里的一样——前者是请求配置,后者则是底层的请求对象

自定义 validateStatus()

当然,对于有响应的请求,2xx 状态码进入 then,之外的状态码进入 catch 是 axios 的默认配置——通过 validateStatus() 设置的。

// `validateStatus` defines whether to resolve or reject the promise for a given
// HTTP response status code. If `validateStatus` returns `true` (or is set to `null`
// or `undefined`), the promise will be resolved; otherwise, the promise will be
// rejected.
validateStatus: function (status) {
  return status >= 200 && status < 300; // default
},

在 axios 内部,当接收到响应后,会将响应码传入 validateStatus() 函数校验。返回 true,就表示请求成功,否则表示请求失败。

你可以自由调整这里的判断,决定哪类响应可以作为成功的请求处理。

比如,将返回状态码 4xx 的请求也看做是成功的。

axios.get('https://httpstat.us/404', {
  validateStatus: function (status) {
    return status < 500; // Resolve only if the status code is less than 500
  }
})
  .then(res => {
    console.log(res)
  })

效果:

我们设置可以将 validateStatus 设置为 null,将所有有响应返回的请求都看作是成功的,这样也能进入 .then() 中处理了。

axios.get('https://httpstat.us/500', {
  validateStatus: null
})
  .then(res => {
    console.log(res)
  })

效果:

无响应的异常

不过某些请求是没有响应返回的。比如:网络中断、跨域错误、超时、取消请求等。这类异常请求都没有响应返回,但都会落入到 .catch() 里。

网络中断

先以网络中断的情况举例。

axios.get('https://httpstat.us/200?sleep=10000', {
})
  .catch(err => {
    console.log(err)
  })

我们模拟了一个耗时 10s 的请求,在此期间,我们将电脑的网络断掉。就能看到效果。

这个时候可以发现,catch() 中接收到 Axios 对象是没有 response 属性的,说明没有服务响应。同时,错误信息是“Network Error”,也就是网络服务。

当然,无效地址以及跨域错误,也报错 “Network Error”

超时报错

再演示一个请求超时的案例。

axios.get('https://httpstat.us/200?sleep=10000', {
  timeout: 1000
})
  .catch(err => {
    console.log(err)
  })

我们模拟了个 10s 返回的请求,而超时限制设置在了 1s。运行代码,效果如下:

显而易见,错误里依然没有 response 属性,错误的消息也很清晰的说明了问题:"过了 1s 的超时限制了"。

取消请求

axios 中还提供了取消请求的方案。

const controller = new AbortController();

axios.get('https://httpstat.us/200?sleep=10000', {
  signal: controller.signal
})
  .catch(err => {
    console.log(err)
  })

controller.abort();

效果如下:

catch() 捕获到的是一个 CanceledError 对象,它继承了 AxiosError,这样我们就能单独判断这类自动取消的情况了。注意,这里依然是没有 response 属性的。

当然,axios 中还有一个旧的取消方案——使用 CancelToken。

axios.get('https://httpstat.us/200?sleep=10000', {
  cancelToken: source.token
})
  .catch(res => {
    console.log(res)
  })

source.cancel();

相比较于 AbortController 触发的取消,少了 config 和 request 属性。

以上,我们就列完了 aioxs 中各类异常请求的场景及表现。官方仓库 README 的 Handling Errors 也对此做了归纳。

axios.get('/user/12345')
  .catch(function (error) {
    if (error.response) {
      // 请求发出,并且得到服务器响应
      // 响应码在 2xx 之外(默认)
      console.log(error.response.data);
      console.log(error.response.status);
      console.log(error.response.headers);
    } else if (error.request) {
      // 请求发出,但没有响应返回
      // `error.request` 对应底层请求对象。浏览器环境是 XMLHttpRequest 实例,Node.js 环境下则是 http.ClientRequest 实例
      console.log(error.request);
    } else {
      // 在请求准备/响应处理阶段出错了
      console.log('Error', error.message);
    }
    console.log(error.config);
  });

接下来就来分析 axios 中是如何实现请求的异常处理的。

源码分析

我们还是以 axios 的浏览器端实现(lib/adapters/xhr.js)为例。

AxiosError

通过前面的学习,我们知道 axios 抛出的异常是基于 Error 基类封装的 AxiosError,其源代码位于 /lib/core/AxiosError.js。

function AxiosError(message, code, config, request, response) {
  // 1)
  Error.call(this);
  // 2)
  if (Error.captureStackTrace) {
    Error.captureStackTrace(this, this.constructor);
  } else {
    this.stack = (new Error()).stack;
  }
  // 3)
  this.message = message;
  this.name = 'AxiosError';
  // 4)
  code && (this.code = code);
  config && (this.config = config);
  request && (this.request = request);
  response && (this.response = response);
}

简单做一些说明:

  • Error.call(this) 的作用类似调用父级构造函数,AxiosError 实例原型也成 Error 实例了
  • 收集报错栈信息,优先以 Error.captureStackTrace 方式收集,方便排查问题
  • 设置常规属性 message 和 name
  • 扩展出 code、code、code 和 response,这些都是可选的

当然 AxiosError 还有其他代码,因为本文不涉及,就不再赘述。

介绍完 AxiosError,就可以分析 axios 中是如何抛出 AxiosError 的了。

XMLHttpRequest 对象

在能够抛出异常之前,我们需要先创建请求对象 request。

// https://github.com/axios/axios/blob/v1.6.8/lib/adapters/xhr.js#L76
let request = new XMLHttpRequest();

浏览器环境,request 就是 XMLHttpRequest 实例,接下来的异常处理都是基于 request 上的监听事件捕获的。

无响应异常的处理

接下来,我们先讲无响应异常的处理,因为它们的相对逻辑比较简单。

网络异常

这类异常包括:网络中断、跨域错误以及请求地址错误。通过监听 request 的 onerror 事件实现:

// /v1.6.8/lib/adapters/xhr.js#L158-L166
// Handle low level network errors
request.onerror = function handleError() {
  // Real errors are hidden from us by the browser
  // onerror should only fire if it's a network error
  reject(new AxiosError('Network Error', AxiosError.ERR_NETWORK, config, request));

  // Clean up request
  request = null;
};

直接返回了一个 reject 状态的 Promise,表示请求失败。并返回了 CODE 值为 ERR_NETWORK 的 AxiosError 对象。

超时处理

再来看看对超时的处理,监听了 ontimeout 事件。

// /v1.6.8/lib/adapters/xhr.js#L168-L183
// Handle timeout
request.ontimeout = function handleTimeout() {
  let timeoutErrorMessage = config.timeout ? 'timeout of ' + config.timeout + 'ms exceeded' : 'timeout exceeded';
  const transitional = config.transitional || transitionalDefaults;
  if (config.timeoutErrorMessage) {
    timeoutErrorMessage = config.timeoutErrorMessage;
  }
  reject(new AxiosError(
    timeoutErrorMessage,
    transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED,
    config,
    request));

  // Clean up request
  request = null;
};

处理也很简单,同样是 reject Promise,同时抛出一个 CODE 值为 ECONNABORTED 的 AxiosError 对象。

transitional 配置对象是为了向后兼容才保留的,已不再推荐使用,所以你可以忽略这部分你的判断逻辑。

另外,你还可以通过传入 config.timeoutErrorMessage 配置,自定义超时报错消息。

取消请求

取消请求依赖的是监听 onabort 事件。

// /v1.6.8/lib/adapters/xhr.js#L146-L156
// Handle browser request cancellation (as opposed to a manual cancellation)
request.onabort = function handleAbort() {
  if (!request) {
    return;
  }

  reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, request));

  // Clean up request
  request = null;
};

当你调用 request 上的 abort() 方法时,就会触发这个事件调用。

在 axios 内部,不管你是通过 signal 还是通过 cancelToken(已弃用),内部都是通过调用 request.abort() 来中止请求的。

取消请求的报错 CODE 值跟超时一样也是 ECONNABORTED,不过报错消息是“Request aborted”。这样你就能区分这次请求是浏览器取消的还是人工取消的了。

// /v1.6.8/lib/adapters/xhr.js#L231-L247
if (config.cancelToken || config.signal) {
  // Handle cancellation
  // eslint-disable-next-line func-names
  onCanceled = cancel => {
    if (!request) {
      return;
    }
    reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel);
    request.abort();
    request = null;
  };

  config.cancelToken && config.cancelToken.subscribe(onCanceled);
  if (config.signal) {
    config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
  }
}

再来看看,有响应的异常处理逻辑。

有响应异常的处理

axios 内部通过监听 onloadend 事件来处理有响应的异常请求。

// /v1.6.8/lib/adapters/xhr.js#L125
request.onloadend = onloadend

不管当前请求是否成功,onloadend 回调总是会调用,这里其实是可以使用 onload 事件替代的。

request.onload = onloadend

之所以有这部分逻辑是为了向后兼容,因为 axios 中这部分的完整逻辑是这样的。

if ('onloadend' in request) {
  // Use onloadend if available
  request.onloadend = onloadend;
} else {
  // Listen for ready state to emulate onloadend
  request.onreadystatechange = function handleLoad() {
    // ...
    
    // readystate handler is calling before onerror or ontimeout handlers,
    // so we should call onloadend on the next 'tick'
    setTimeout(onloadend);
  }
}

OK,我们继续看 onloadend 函数的内容:

// /v1.6.8/lib/adapters/xhr.js#L92-L121
function onloadend() {
  // 1)
  if (!request) {
    return;
  }
  
  // 2)
  // Prepare the response
  const responseHeaders = AxiosHeaders.from(
    'getAllResponseHeaders' in request && request.getAllResponseHeaders()
  );
  const responseData = !responseType || responseType === 'text' || responseType === 'json' ?
    request.responseText : request.response;
  const response = {
    data: responseData,
    status: request.status,
    statusText: request.statusText,
    headers: responseHeaders,
    config,
    request
  };
  
  // 3)
  settle(function _resolve(value) {
    resolve(value);
    done();
  }, function _reject(err) {
    reject(err);
    done();
  }, response);
  
  // Clean up request
  request = null;
}
  • 这里做了 request 的非空判断。因为 onloadend 在调用之前,可以已经在其他的回调事件中处理了,直接返回即可
  • 这里则是准备返回的响应数据。先收集响应头数据,再获得响应数据,最后拼成 Respoonse 对象返回。注意,当 responseType 是 "json" 时,响应数据返回的是 request.responseText,是个字符串,这会在下一步处理。
  • 这里我们将拼接的 response 交由 settle 函数处理,并由它决定最终是成功请求(resolve(err))还是失败请求(reject(err)

settle 函数位于 lib/core/settle.js:

/**
 * Resolve or reject a Promise based on response status.
 *
 * @param {Function} resolve A function that resolves the promise.
 * @param {Function} reject A function that rejects the promise.
 * @param {object} response The response.
 *
 * @returns {object} The response.
 */
export default function settle(resolve, reject, response) {
  // 1)
  const validateStatus = response.config.validateStatus;
  // 2)
  if (!response.status || !validateStatus || validateStatus(response.status)) {
    resolve(response);
  // 3)
  } else {
    reject(new AxiosError(
      'Request failed with status code ' + response.status,
      [AxiosError.ERR_BAD_REQUEST, AxiosError.ERR_BAD_RESPONSE][Math.floor(response.status / 100) - 4],
      response.config,
      response.request,
      response
    ));
  }
}
  • 我们首先拿到了 validateStatus 配置。这是我们判断请求成功与否的关键
  • 这个 if 通过就把传入的 response 直接丢出去,表示请求成功了。跟这个判断逻辑,我们可以知道,当 validateStatus 为空(nullundefined),所有响应都会认为是成功的被返回
  • 否则,没有通过校验那就表示请求失败了。报错消息类似 'Request failed with status code xxx';4xx 状态码的返回 CODE 是 ERR_BAD_REQUEST,5xx 状态码的返回 CODE 是 ERR_BAD_RESPONSE;最后我们还把 response 作为 AxiosError 的 response 属性传入了进来

至此,我们就讲完了 axios 中的异常处理逻辑了。

总结

本文介绍了 axios 请求过程中可能会出现的各种异常场景。

axios 异常场景按照有无响应分 2 类:有响应异常和无响应异常。有响应异常就是指那些能成功接收到服务器响应状态码的请求,包括常见的 2xx、4xx 和 5xx;无响应异常则包括网络中断、无效地址、跨域错误、超时、取消等场景下的错误,这些都是接受不到服务器响应的。

然后,我们从浏览器端实现出发,介绍了 AxiosError、分析了抛出 AxiosError 异常的时机与方式。

以上就是详解axios是如何处理异常的的详细内容,更多关于axios处理异常的资料请关注脚本之家其它相关文章!

相关文章

  • JS ES6展开运算符的几个妙用

    JS ES6展开运算符的几个妙用

    这篇文章主要介绍了JS ES6展开运算符的几个妙用,想了解ES6的同学,可以参考下
    2021-04-04
  • JS实现可针对算术表达式求值的计算器功能示例

    JS实现可针对算术表达式求值的计算器功能示例

    这篇文章主要介绍了JS实现可针对算术表达式求值的计算器功能,可实现基本的数字四则运算功能,涉及javascript基本数值运算与流程控制、判断等操作技巧,需要的朋友可以参考下
    2018-09-09
  • webpack+vue2构建vue项目骨架的方法

    webpack+vue2构建vue项目骨架的方法

    本篇文章主要介绍了webpack+vue2构建vue项目骨架的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-01-01
  • 微信小程序全屏滚动字幕的实现方法详解

    微信小程序全屏滚动字幕的实现方法详解

    这篇文章主要介绍了微信小程序全屏滚动字幕的实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-08-08
  • 图文详解JavaScript的原型对象及原型链

    图文详解JavaScript的原型对象及原型链

    许多人对JavaScript的原型及原型链仍感到困惑,网上的文章又大多长篇大论,令读者不明觉厉。下面小编将用最简洁明了的图文介绍JavaScript的原型及原型链。
    2016-08-08
  • 前端无感知刷新token以及超时自动退出实现方案

    前端无感知刷新token以及超时自动退出实现方案

    前端需要做到无感刷新token,即刷token时要做到用户无感知,避免频繁登录,下面这篇文章主要给大家介绍了关于前端无感知刷新token以及超时自动退出的实现方案,需要的朋友可以参考下
    2024-01-01
  • Apply an AutoFormat to an Excel Spreadsheet

    Apply an AutoFormat to an Excel Spreadsheet

    Apply an AutoFormat to an Excel Spreadsheet...
    2007-06-06
  • JS对象类型之Error错误对象的用法详解

    JS对象类型之Error错误对象的用法详解

    error对象是JavaScript的原生对象,当程序解析和运行过程中发生了错误,JS引擎就会自动产生并抛出一个error对象的实例,并且程序会终止在错误发生的地方,本文给大家介绍了JS Error错误对象的用法,需要的朋友可以参考下
    2024-04-04
  • 微信小程序实现图片懒加载的示例代码

    微信小程序实现图片懒加载的示例代码

    本篇文章主要介绍了微信小程序实现图片懒加载的示例代码,实现的原理是通过页面预加载图片,对用户体验度会有一定的提高,具有一定的参考价值,有兴趣可以了解一下
    2017-12-12
  • 新手快速入门JavaScript装饰者模式与AOP

    新手快速入门JavaScript装饰者模式与AOP

    这篇文章主要介绍了新手快速入门JavaScript装饰者模式与AOP,在不改变对象)的情况下动态的为其添加功能,这就是装饰者模式,下面小编带大家来深入学习一下吧
    2019-06-06

最新评论