乐趣区

关于前端:axios-底层是如何判断是否应该报错的

前几天面试的时候面试官问了两个问题:

  • axios 底层封装的是什么?
  • 什么状况下 axios 会报错?

在此之前只看过 axios 底层 Promise 的天然 G 了,面试完就开始看 axios 的源代码。

本文基于 axios v1.1.3

底层封装

首先来看看 axios 底层到底封装的什么

axios 底层封装的申请有两种:XMLHttpRequestNode.jshttp/https,位于 lib/adapters 文件夹内。接下来咱们以 XMLHttpRequest 的封装为例看下什么状况下会报错

报错状况

XMLHttpRequest 的封装在 lib/adapters/xhr.js 文件夹内的 xhrAdapter,该函数间接返回一个 Promise

export default function xhrAdapter(config) {return new Promise(function dispatchXhrRequest(resolve, reject) {...});

与报错相干的逻辑如下

export default function xhrAdapter(config) {return new Promise(function dispatchXhrRequest(resolve, reject) {
        ...
        // 初始化一个 XMLHttpRequest 对象
        let request = new XMLHttpRequest();
        ...
        // 初始化一个新创建的申请
        request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);
        ...
        // 用于解决申请胜利时的状况
        function onloadend() {
            ...
            settle(function _resolve(value) {resolve(value);
                done();}, function _reject(err) {reject(err);
                done();}, response);
            ...
        }
        
        // 申请胜利时的回调
        if ('onloadend' in request) {
            // Use onloadend if available
            request.onloadend = onloadend;
        } else {
            // Listen for ready state to emulate onloadend
            request.onreadystatechange = function handleLoad() {if (!request || request.readyState !== 4) {return;}

                if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {return;}
                setTimeout(onloadend);
            };
        }
            
        // 解决勾销申请
        request.onabort = function handleAbort() {if (!request) {return;}

            reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, request));
            ...
        };
        // 解决网络谬误
        request.onerror = function handleError() {reject(new AxiosError('Network Error', AxiosError.ERR_NETWORK, config, request));
            ...
        };
        // 解决超时
        request.ontimeout = function handleTimeout() {
            ...
            reject(new AxiosError(
                timeoutErrorMessage,
                transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED,
                  config,
                request));
            ...
        };
        // 发送申请
        request.send(requestData || null);
    });
}

能够看到 onabort onerror ontimeout 三个回调都是间接解决一下而后调用 reject,然而 onloadend 并不是这样,咱们来认真看一下这一部分

        // 用于解决申请胜利时的状况
        function onloadend() {
            ...
            settle(function _resolve(value) {resolve(value);
                done();}, function _reject(err) {reject(err);
                done();}, response);
            ...
        }
        
        // 申请胜利时的回调
        if ('onloadend' in request) {
            // Use onloadend if available
            request.onloadend = onloadend;
        } else {
            // Listen for ready state to emulate onloadend
            request.onreadystatechange = function handleLoad() {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

export default function settle(resolve, reject, response) {
    const validateStatus = response.config.validateStatus;
    if (!response.status || !validateStatus || validateStatus(response.status)) {resolve(response);
    } 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
        ));
    }
}

能够看到 if (!response.status || !validateStatus || validateStatus(response.status)),即当以下三个条件满足其一时即可 resolve

  • 服务器返回的响应中不存在状态码或状态码为 0
  • axios 的申请配置中 validateStatus 属性为空
  • validateStatus 函数返回 true

否则则 reject。那么 validateStatus 又是什么?咱们来看 axios 的默认申请配置 default,位于 lib/defaults/index.js 文件内

const defaults = {validateStatus: function validateStatus(status) {return status >= 200 && status < 300;},
};

能够看到,默认的 validateStatus 是一个函数,当状态码在 [200,300) 时返回 true,否则返回 false

XMLHttpRequest 对象触发事件的程序中 onabort ontimeout onerroronloadend 之前,所以如果产生了异样,则在 onloadend 执行之前曾经调用了 rejectonloadend 中即便调用 resolve 也没有作用了。如果申请胜利则由 onloadend 进行解决。

接下来咱们来看 XMLHttpRequest 对象中没有 onloadend 属性的时候

        // 申请胜利时的回调
        if ('onloadend' in request) {
            // Use onloadend if available
            request.onloadend = onloadend;
        } else {
            // Listen for ready state to emulate onloadend
            request.onreadystatechange = function handleLoad() {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: 协定则间接返回,接下来将 onloadend 退出到宏工作队列中去。

通过控制台模仿可知模仿的 onloadend 产生在 onloadend 之后

总结

axios 默认底层封装了 XMLHttpRequesthttp/https 库,默认状况下只有以下四种状况会报错

  • 勾销申请
  • 网络谬误
  • 超时
  • 状态码不在 [200,300) 区间内
退出移动版