前几天面试的时候,面试官问了两个问题:axios封装的底层是什么?axios在什么情况下会报错?在此之前,我只在axios底部看到了Promise的天然G。面试完就开始看axios的源码。本文基于axiosv1.1.3的底层封装。首先我们看一下axios底层封装了哪些东西。axios底层包中有两类请求:XMLHttpRequest和Node.js的http/https,位于lib/adapters文件夹下。下面我们以封装XMLHttpRequest为例,看看在什么情况下会报错。XMLHttpRequest封装在lib/adapters/xhr.js文件夹下的xhrAdapter中。这个函数直接返回一个PromiseexportdefaultfunctionxhrAdapter(config){returnnewPromise(functiondispatchXhrRequest(resolve,reject){...});报错相关的逻辑如下/初始化一个新创建的请求request.open(config.method.toUpperCase(),buildURL(fullPath,config.params,config.paramsSerializer),true);...//用于处理请求成功时的情况函数onloadend(){...settle(function_resolve(value){resolve(value);done();},function_reject(err){reject(err);done();},response);...}//请求成功回调('onloadend'inrequest){//如果可用则使用onloadendrequest.onloadend=onloadend;}else{//监听就绪状态以模拟onloadendrequest.onreadystatechange=functionhandleLoad(){if(!request||request.readyState!==4){return;}if(request.status===0&&!(request.responseURL&&request.responseURL.indexOf('file:')===0)){返回;}setTimeout(onloadend);};}//处理取消请求request.onabort=functionhandleAbort(){if(!request){return;}reject(newAxiosError('Requestaborted',AxiosError.ECONNABORTED,config,request));...};//处理网络错误request.onerror=functionhandleError(){reject(newAxiosError('NetworkE错误',AxiosError.ERR_NETWORK,配置,请求));...};//处理超时request.ontimeout=functionhandleTimeout(){...reject(newAxiosError(timeoutErrorMessage,transitional.clarifyTimeoutError?AxiosError.ETIMEDOUT:AxiosError.ECONNABORTED,config,request));...};//发送请求request.send(requestData||null);});}可以看到直接处理onabortonerrorontimeout三个回调然后调用reject,但是onloadend不是这样的,我们仔细看这部分//用来处理请求成功时的情况函数onloadend(){...settle(function_resolve(value){resolve(value);done();},function_reject(err){reject(err);done();},response);...}//请求成功时回调if('onloadend'inrequest){//使用onloadendifavailablerequest.onloadend=onloadend;}else{//监听就绪状态以模拟onloadendrequest.onreadystatechange=functionhandleLoad(){if(!request||request.readyState!==4){return;}if(request.status===0&&!(request.responseURL&&request.responseURL.indexOf('file:')===0)){return;}setTimeout(onloadend);};}源码中首先定义了一个onloadend函数,然后判断request对象中是否有onloadend属性。我个人的猜测是它可能与旧版本的浏览器兼容。如果此属性不存在,将使用另一种可能性。这些事会晚一些讨论。继续看onloadend函数,其核心是调用settle函数,位于lib/core/settle.js中。导出默认函数settle(resolve,reject,response){constvalidateStatus=response.config.validateStatus;如果(!response.status||!validateStatus||validateStatus(response.status)){resolve(response);}else{reject(newAxiosError('请求失败,状态码'+response.status,[AxiosError.ERR_BAD_REQUEST,AxiosError.ERR_BAD_RESPONSE][Math.floor(response.status/100)-4],response.config,response.请求、响应));}}可以看到if(!response.status||!validateStatus||validateStatus(response.status)),即满足以下三个条件之一,即可解析。服务端返回的response中没有状态码或status在code为0axios的请求配置中,validateStatus属性为空,validateStatus函数返回true,否则拒绝。那么什么是validateStatus?我们看下axios默认的请求配置default,位于lib/defaults/index.js文件中constdefaults={validateStatus:functionvalidateStatus(status){returnstatus>=200&&status<300;},};可以看到,默认的validateStatus是一个函数,当状态码在[200,300]时返回true,否则返回false。在XMLHttpRequest对象触发事件的顺序中,onabortontimeoutonerror在onloadend之前,所以如果出现异常,在onloadend执行之前已经调用了reject,即使在onloadend中调用resolve也没有效果。如果请求成功,则由onloadend处理。接下来我们看下当XMLHttpRequest对象中没有onloadend属性时//请求成功时的回调if('onloadend'inrequest){//如果可用则使用onloadendrequest.onloadend=onloadend;}else{//监听就绪状态以模拟onloadendrequest.onreadystatechange=functionhandleLoad(){if(!request||request.readyState!==4){return;}if(request.status===0&&!(request.responseURL&&request.responseURL.indexOf('file:')===0)){return;}setTimeout(onloadend);};}根据源码中的注释,我们可以知道这里使用了onreadystatechange来模拟onloadend。这里如果readyState不为4,不做任何操作直接返回。readyState为4表示请求已结束,无论是成功还是失败。if(request.status===0&&!(request.responseURL&&request.responseURL.indexOf('file:')===0))是因为如果使用file:协议,状态码会是0,所以如果状态码为0且不是file:protocol,则直接返回,然后将onloadend添加到宏任务队列中。通过控制台模拟可以看出,模拟的onloadend发生在onloadend之后。总结axios默认在底层封装了XMLHttpRequest和http/https库。默认只有以下四种情况会报错取消请求。网络错误超时状态码不在[200,300)范围内
