axios 底层是如何判断是否应该报错的

1 篇文章 0 订阅
1 篇文章 0 订阅

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

  • 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) 区间内
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值