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 为空(
null
或undefined
),所有响应都会认为是成功的被返回 - 否则,没有通过校验那就表示请求失败了。报错消息类似
'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处理异常的资料请关注代码网其它相关文章!
发表评论