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

  • 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) 区间内