• axios源码记录 学习笔记


    axios源码记录

    前言

    最近都再看算法的内容,突然发现自己axios方面的知识还没有学习好,也攒下了不少偏笔记,在这里浅浅记录一下自己学习的历程。

    axios 初始化

    当我们npm install axios后,打开axios的 package.json可以找得到axios的入口文件 "main": "index.js"

    我们顺着这个文件去一步步理清axios的内容

    index.js

    图片.png

    随之我们进入index.js的文件中

    axios.js

    引入部分

    图片.png

    文件最上面的部分是各种文件的引入,包括配默认置文件,工具函数,Axios主文件

    下面的部分中可以看得出来,axios是一个函数

    是生成实例对象 axiosaxios.Axiosaxios.create等。

    图片.png

    函数的内容如图

    • 定义了一个 Axios实例
    • 使用bind函数创建instance,返回一个新的 wrap 函数,
    • 使用extend函数将Axios原型和实例上的内容绑定到instance上,
      • 也就是为什么 有 axios.get 等别名方法
      • 也就是为什么默认配置 axios.defaults 和拦截器 axios.interceptors 可以使用的原因
    • 定义调用axios.create()时执行的内容
    • 最后返回实例对象

    剩下的文件中的部分就是为 axios函数添加方法

    取消相关API实现,还有allspread、导出等实现。
    图片.png
    这些还没有去研究,之后再写吧

    工具函数

    源码中有一些自定义的工具函数,和平时我们认知的有一点不一样,把他们单独拿出来讲一下

    bind函数

    图片.png

    这里重写的bind函数并不像我们平时的bind函数一样,参数的位置有一丢丢不一样,但也容易理解

    utils.extend

    我们再utils文件中找到extend

    图片.png

    forEach

    图片.png

    加了大量的注释,应该一下子就能看懂了,

    调用forEach时

    forEach(遍历的内容,处理的函数(值,索引,遍历的内容){处理细节}, 是否全是自身的key)
    
    • 1

    看完这三个 方法工具 上面的 createInstance函数 应该就能看懂了

    merge

    把多个对象的值都放到一起来。

    图片.png

    核心–构造函数 Axios

    在上面的axios.js的文件中

    图片.png

    这一步是非常关键的,其中出现了很多次的 Axios。同时这也是 axios的关键所在

    Axios构造函数

    图片.png

    Axios文件中主要内容在这个Axios构造函数的构建。

    里面包括了

    • 调用 axios('url',config),axios({config})的配置方式
    • 默认配置和传入配置的合并
    • 请求方法的设置
    • 请求头配置
    • 拦截器注册
    • 拦截器操作的promise
    • get,post,delete等方法绑定到实例的原型上
    • 暴露处Axios

    这些内容,如果把这些内容掌握了axios就差不多了

    配置对象config的处理

    无论是直接通过axios('url',{config})的方法调用 axios 还是 axios({config}) 或者是 axios.get()的方式使用axios , 都绕不过config的配置

    直接调用axios({})或者axios(“url”,{})

    图片.png

    使用这两种方式时,原理上就是直接调用Axios的request方法

    这时会对调用时,传入的参数进行判断,

    如果第一个参数是字符串形式 即用 axios("url",{})的方法,就会将第一个参数中的 url 复制到 第二个参数的config中

    如果第一个字符串不存在 即用 axios({})的方式,就将第一个参数的值,直接给了config,

    之后还要将传入配置和axios默认配置进行合并。

    通过 axios.get()调用

    图片.png

    Axios的原型中绑定了一些列的请求方法,调用这些方法会返回一个 this.request同样也是执行请求,会将调用时传入的一系列参数都合并起来,组成一个 config对象

    mergeConfig.js

    图片.png

    图片.png

    图片.png
    mergeConfig中有争对不同的配置型,确定合并的方式,有一个Map的映射表,还有处理的forEach

    将所有的配置文件(用户传入的,默认配置的等等)进行一个合并,最终返回合并后的结果

    request()

    Axios.js中,Axios构造函数中的 request()时最重要的模块,下面是我对源码详细注释后的 request实力函数

      request(configOrUrl, config) {
        /*eslint no-param-reassign:0*/
        // Allow for axios('example/url'[, config]) a la fetch API
        // 对url的设置,争对调用axios时是否将 url 分出去,做出区分配置
        if (typeof configOrUrl === 'string') {
          config = config || {};
          config.url = configOrUrl;
        } else {
          config = configOrUrl || {};
        }
    
        //将传入的配置和默认配置合并
        // mergeConfig 会将参数中的配置合并在一起,
        config = mergeConfig(this.defaults, config);
    
        const transitional = config.transitional;
    
        if (transitional !== undefined) {
          validator.assertOptions(transitional, {
            silentJSONParsing: validators.transitional(validators.boolean),
            forcedJSONParsing: validators.transitional(validators.boolean),
            clarifyTimeoutError: validators.transitional(validators.boolean)
          }, false);
        }
    
        // Set config.method
        // 确定 config 中的方法名
        config.method = (config.method || this.defaults.method || 'get').toLowerCase();
    
        // 下面的这堆都是对header进行处理,最后生成请求用的headers
        // Flatten headers
        const defaultHeaders = config.headers && utils.merge(
          config.headers.common,
          config.headers[config.method]
        );
    
        defaultHeaders && utils.forEach(
          ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
          function cleanHeaderConfig(method) {
            delete config.headers[method];
          }
        );
    
        config.headers = new AxiosHeaders(config.headers, defaultHeaders);
    
        // filter out skipped interceptors
        const requestInterceptorChain = [];
        // 同步请求拦截器
        let synchronousRequestInterceptors = true;
    
        // 这里的 forEach 不是数组的forEach方法,而是 request 创建的实例 InterceptorManager 中的 实例方法forEach,
        // 遍历的是其中的实例属性 handlers数组。下面的响应拦截器也是一样的
        this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
          // 如果用户没有特殊的进行配置的话这里一般不会执行
          if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {
            return;
          }
    
          // interceptor.synchronous 是用户 通过 axios.interceptors.request.use 注册时填入的第三个参数 option中的 synchronous 值,一般默认为false
          // 表示不是同步全球拦截器
          synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;
    
          // 在这里将 请求拦截器的内容添加到 chrequestInterceptorChain 中,一组一组的添加
          // 请求拦截器的添加是通过 unshift 添加到 chain 的前面的
          // 由于这种unshift的方法,后注册的请求拦截器会 比 先注册的拦截器的执行快
          requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
        });
    
        const responseInterceptorChain = [];
        // 在这里将 响应拦截器的内容添加到 responseInterceptorChain 中,一组一组的添加
    
        this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
          // 这里也是将 response 中的内容全部添加到 chain 的后面
          responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
        });
    
        let promise;
        let i = 0;
        let len;
    
        // 这里的 synchronousRequestInterceptors 同步请求拦截器 一般指都为 false,然后执行里面的内容
        if (!synchronousRequestInterceptors) {
          // 先把 chain 初始化,第一个是请求,第二个是undefined
          const chain = [dispatchRequest.bind(this), undefined];
          // 然后在前面添加 请求拦截器的 reject和resolve
          chain.unshift.apply(chain, requestInterceptorChain);
          // 然后在后面添加 请求拦截器的 reject和resolve
          chain.push.apply(chain, responseInterceptorChain);
          len = chain.length;
    
          // 创建一个成功的 promise 并把 合并好的config 作为参数传给.then的成功回调
          // 这个config 参数就是 给到所有拦截器的config参数
          promise = Promise.resolve(config);
    
          while (i < len) {
            // 最后再把这些内容 依次调用.then执行,
            // 把 chain中的内容, 一组一组的(拦截器的成功回调和失败回调为一组),添加到.then的 resolve函数和reject函数中
            promise = promise.then(chain[i++], chain[i++]);
          }
          // 最终返回一个这个promise对象
          return promise;
        }
    
        // 从这里往下就是设置的是  synchronousRequestInterceptors 为true时执行的内容
        // 会以同步的方式执行拦截器,而不是 .then 后放入异步队列
        len = requestInterceptorChain.length;
    
        let newConfig = config;
    
        i = 0;
    
        while (i < len) {
          const onFulfilled = requestInterceptorChain[i++];
          const onRejected = requestInterceptorChain[i++];
          try {
            newConfig = onFulfilled(newConfig);
          } catch (error) {
            onRejected.call(this, error);
            break;
          }
        }
    
        ///
        try {
          //dispatchRequest
          promise = dispatchRequest.call(this, newConfig);
        } catch (error) {
          return Promise.reject(error);
        }
    
        i = 0;
        len = responseInterceptorChain.length;
    
        while (i < len) {
          promise = promise.then(responseInterceptorChain[i++], responseInterceptorChain[i++]);
        }
    
        return promise;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139

    我们将如上的内容解读完后,了解到这一步中就是形成了一个 promise链,其中的 dispatchRequest 才是请求的更深层。

    dispatchRequest

    dispatchRequest.js
    图片.png

    dispatchRequest中主要是对 配置data 和 返回的data的处理,发送请求时在 adapter 里面进一步划分的,

    值得注意的内容是 config.transformRequest 中一系列函数 对data content-type的处理

    adapter

    defaults/index.js中我们可以找到 adapter的内容

    图片.png

    我们顺着这个线索一步步往下

    同样在 defaults/index.js

    图片.png
    这里就是通过两个if语句来判断当前的环境是 浏览器环境还是node环境 。通常都是使用 xhr 在浏览器端发送请求

    adapters/index.js

    图片.png
    最后让我们进入到 xhr 的请求中看一看具体的发送请求过程

    xhr.js

    这里是我们真正发起请求的地方

    export default function xhrAdapter(config) {
      // 请求返回一个promise对象
      return new Promise(function dispatchXhrRequest(resolve, reject) {
        // 在这里获取config中的data内容,配置requestHeaders,responseType等等
        let requestData = config.data;
        const requestHeaders = AxiosHeaders.from(config.headers).normalize();
        const responseType = config.responseType;
        let onCanceled;
    
        function done() {
          if (config.cancelToken) {
            config.cancelToken.unsubscribe(onCanceled);
          }
    
          if (config.signal) {
            config.signal.removeEventListener('abort', onCanceled);
          }
        }
    
        if (utils.isFormData(requestData) && platform.isStandardBrowserEnv) {
          requestHeaders.setContentType(false); // Let the browser set it
        }
    
        // 在这里我们就嫩见到我们所熟知的 ajax 请求的部分 new XMLHttpRequest
        let request = new XMLHttpRequest();
    
        // HTTP basic authentication
        if (config.auth) {
          const username = config.auth.username || '';
          const password = config.auth.password ? unescape(encodeURIComponent(config.auth.password)) : '';
          requestHeaders.set('Authorization', 'Basic ' + btoa(username + ':' + password));
        }
    
        // 构建一个完整的路径
        const fullPath = buildFullPath(config.baseURL, config.url);
    
        // 发起初始化一个ajax请求,method url、参数、序列化函数
        request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);
    
        // Set the request timeout in MS
        // 设置请求超时
        request.timeout = config.timeout;
    
        // 注意 promise的 resolve() 和 reject() 函数是在这里面执行的
        function onloadend() {
          // 
          if (!request) {
            return;
          }
          // Prepare the response
          // 准备 请求返回的结果 response
          const responseHeaders = AxiosHeaders.from(
            'getAllResponseHeaders' in request && request.getAllResponseHeaders()
          );
          const responseData = !responseType || responseType === 'text' || responseType === 'json' ?
            request.responseText : request.response;  //在这里判断 responseType 是什么值,然后获取对应的 请求结果
          // 把结果进行封装一下
          const response = {
            data: responseData,
            status: request.status,
            statusText: request.statusText,
            headers: responseHeaders,
            config,
            request
          };
          // 当readystatus == 4时,才会执行
          // settle的参数为 成功回调,失败回调,response
          settle(function _resolve(value) {
            resolve(value);
            done();
          }, function _reject(err) {
            reject(err);
            done();
          }, response);
    
          // Clean up request
          request = null;
        }
    
        // 
        if ('onloadend' in request) {
          // Use onloadend if available
          // ajax 的 onloadend 会在请求结束时触发
          request.onloadend = onloadend;
        } else {
          // Listen for ready state to emulate onloadend
          // 如果没有定义 ajax的onloadend的话,就在下面 监听 onreadystatechange,status 如果结果成功(状态时4)的话,就异步执行onloadend
          request.onreadystatechange = function handleLoad() {
            if (!request || request.readyState !== 4) {
              return;
            }
    
            // The request errored out and we didn't get a response, this will be
            // handled by onerror instead
            // With one exception: request that using file: protocol, most browsers
            // will return status as 0 even though it's a successful request
            if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {
              return;
            }
            // readystate handler is calling before onerror or ontimeout handlers,
            // so we should call onloadend on the next 'tick'
            setTimeout(onloadend);
          };
        }
    
        // 下面都是定义在不同状态下的 ajax 的处理情况 例如 调用 ajax 的onabort,onerror,ontimeout等等
        // 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;
        };
    
        // 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;
        };
    
        // 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;
        };
    
        // Add xsrf header
        // This is only done if running in a standard browser environment.
        // Specifically not if we're in a web worker, or react-native.
        if (platform.isStandardBrowserEnv) {
          // Add xsrf header
          const xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath))
            && config.xsrfCookieName && cookies.read(config.xsrfCookieName);
    
          if (xsrfValue) {
            requestHeaders.set(config.xsrfHeaderName, xsrfValue);
          }
        }
    
        // Remove Content-Type if data is undefined
        requestData === undefined && requestHeaders.setContentType(null);
    
        // Add headers to the request
        if ('setRequestHeader' in request) {
          utils.forEach(requestHeaders.toJSON(), function setRequestHeader(val, key) {
            request.setRequestHeader(key, val);
          });
        }
    
        // Add withCredentials to request if needed
        if (!utils.isUndefined(config.withCredentials)) {
          request.withCredentials = !!config.withCredentials;
        }
    
        // Add responseType to request if needed
        if (responseType && responseType !== 'json') {
          request.responseType = config.responseType;
        }
    
        // Handle progress if needed
        if (typeof config.onDownloadProgress === 'function') {
          request.addEventListener('progress', progressEventReducer(config.onDownloadProgress, true));
        }
    
        // Not all browsers support upload events
        if (typeof config.onUploadProgress === 'function' && request.upload) {
          request.upload.addEventListener('progress', progressEventReducer(config.onUploadProgress));
        }
    
        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);
          }
        }
    
        const protocol = parseProtocol(fullPath);
    
        if (protocol && platform.protocols.indexOf(protocol) === -1) {
          reject(new AxiosError('Unsupported protocol ' + protocol + ':', AxiosError.ERR_BAD_REQUEST, config));
          return;
        }
    
    
        // Send the request
        // 在最后,发起请求,
        request.send(requestData || null);
      });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220

    其中设置了很多 ajax 的处理函数,最主要的内容是 onloadend函数 的定义。还有ajax请求发起的几个重要过程。在上面的代码中,已经把需要注意的地方用注释说明了,(建议放到编译器中方便看)

    后续还有很多内容需要补充,待更新…

  • 相关阅读:
    阿里云服务器包年包月收费模式常见问题汇总(官方资料解答)
    ES7~ES12那些事儿
    八、开发者工具与单元测试
    Kibana Query Language (KQL)
    【软件分析第13讲-学习笔记】符号执行 Symbolic Execution
    flann的接口介绍
    Docker初认识
    Python os模块
    时间戳计算年-月-日 时:分
    Tomcat 日志乱码问题解决
  • 原文地址:https://blog.csdn.net/qq_53145332/article/details/127464611