最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 初探axios源码

    正文概述 掘金(1h)   2021-01-25   505

    axios源码分析

    源码目录分析

    • axios.js: axios导出的文件入口
    • Axios.js: 添加原型方法
    • default.js: 默认的axios配置
    • adapters:适配浏览器和非浏览器请求
    • cancel:取消请求类

    axios的工作流程

    初探axios源码

    axios的工作原理

    1. axios

    // axios 和 instance 都会调用createInstance
    function createInstance(defaultConfig) {
    
      var context = new Axios(defaultConfig);
    
      // 绑定Axios原型上的方法request
      // Axios.prototype.request.bind(context), 返回的是方法。
      var instance = bind(Axios.prototype.request, context);
    
      // 将Axios上原型的方法复制到instance上,使得axios能调用get,post,put,delete等方法 axios.get()
      utils.extend(instance, Axios.prototype, context);
    
      // Copy context to instance
      utils.extend(instance, context);
    
      return instance;
    }
    
    // Create the default instance to be exported
    var axios = createInstance(defaults);
    
    // Expose Axios class to allow class inheritance
    axios.Axios = Axios;
    
    // 同样支持axios.create(),创建instance,跟axios没有太大区别,都是通过调用createInstance来的,但是instance没有axios后面添加的cancel,cancelToken等方法
    axios.create = function create(instanceConfig) {
      return createInstance(mergeConfig(axios.defaults, instanceConfig));
    };
    
    // 为axios添加新的属性方法,取消请求。
    axios.Cancel = require('./cancel/Cancel');
    axios.CancelToken = require('./cancel/CancelToken');
    axios.isCancel = require('./cancel/isCancel');
    
    1. 无论是``axios还是instance都会调用createInstance函数,构造Axios的实例,但axios从本质上来说还是一个函数,通过extend,bindAxios`上复制所有的原型扩展方法。
    2. axiosinstance是有区别的,axios包含了后面扩展的CancelCancelToken等方法。
    3. 通过这种方式,在createInstance返回的axios函数的属性上,就已经挂在了Axios原型上的方法和属性了。

    2. Axios 核心类

    function Axios(instanceConfig) {
      this.defaults = instanceConfig;
      // 创建两个拦截器。
      this.interceptors = {
        request: new InterceptorManager(), // 数组存放收集
        response: new InterceptorManager() // 数组存放收集
      };
    }
    
    InterceptorManager.prototype.use = function use(fulfilled, rejected) {
      this.handlers.push({
        fulfilled: fulfilled,
        rejected: rejected
      });
      return this.handlers.length - 1;
    };
    
    1. 收集请求拦截,返回拦截,支持多个拦截操作,触发时会一并进行调用。调用的顺序有所不同,请求拦截是unshift插入,倒序执行,返回拦截是push插入,顺序执行。
    
    
    // 核心,实现请求
    Axios.prototype.request = function request(config) {
      /*eslint no-param-reassign:0*/
     
      if (typeof config === 'string') { // axios('/xxx') 默认是get方法
        config = arguments[1] || {};
        config.url = arguments[0];
      } else {
        // config是个对象
        config = config || {};
      }
      // 合并配置
      config = mergeConfig(this.defaults, config);
    
      // 设置请求方式,统一转成小写
      if (config.method) {
        config.method = config.method.toLowerCase();
      } else if (this.defaults.method) {
        config.method = this.defaults.method.toLowerCase();
      } else {
        config.method = 'get';
      }
    
      // Hook up interceptors middleware
      var chain = [dispatchRequest, undefined];
      var promise = Promise.resolve(config);
    
      // 拦截器会在then返回之前执行
      // 请求拦截器是倒序执行的,unshift
      this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
        chain.unshift(interceptor.fulfilled, interceptor.rejected);
      });
    
      // 返回拦截器是正序执行的,push
      this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
        chain.push(interceptor.fulfilled, interceptor.rejected);
      });
      
      // 支持链式调用
      while (chain.length) {
        promise = promise.then(chain.shift(), chain.shift());
      }
    
      return promise;
    };
    
    1. request做了哪些事,支持config是个字符串,表现形式为axios('/xxx'), 也可以是object, 会去合并mergeConfig(this.defaults, config)默认值。
    2. 统一配置请求方式为toLowerCase, 默认为get方式
    3. 调用interceptors存放的方法
    4. chain.length不为0, 支持链式调用,最终返回promise

    3 dispatchRequest

    module.exports = function dispatchRequest(config) {
      throwIfCancellationRequested(config);
    
      // Ensure headers exist
      config.headers = config.headers || {};
    
      // Transform request data
    
      // data, headers 作为参数放到 transformRequest 中执行
      config.data = transformData(
        config.data,
        config.headers,
        config.transformRequest
      );
    
      // 扁平化操作。
      config.headers = utils.merge(
        config.headers.common || {},
        config.headers[config.method] || {},
        config.headers
      );
    
      utils.forEach(
        ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
        function cleanHeaderConfig(method) {
          delete config.headers[method];
        }
      );
    	
      // 判断出是node环境还是浏览器环境
      var adapter = config.adapter || defaults.adapter;
    
      return adapter(config).then(function onAdapterResolution(response) {
        throwIfCancellationRequested(config);
    
        // Transform response data
        response.data = transformData(
          response.data,
          response.headers,
          config.transformResponse
        );
    
        return response;
      }, function onAdapterRejection(reason) {
        if (!isCancel(reason)) {
          throwIfCancellationRequested(config);
    
          // Transform response data
          if (reason && reason.response) {
            reason.response.data = transformData(
              reason.response.data,
              reason.response.headers,
              config.transformResponse
            );
          }
        }
    
        return Promise.reject(reason);
      });
    };
    
    
    1. dispatchRequest主要做了哪些,将data、headers放入到transformData中执行,再将headers扁平化处理
    2. transformRequest函数数组,对data数据进行转换
    3. adapter判断出是node坏境还是浏览器环境,adapter.then(), 将返回response数据。
    // ajax请求,http请求
    function getDefaultAdapter() {
      var adapter;
      if (typeof XMLHttpRequest !== 'undefined') {
        // For browsers use XHR adapter
        adapter = require('./adapters/xhr');
      } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
        // For node use HTTP adapter
        adapter = require('./adapters/http');
      }
      return adapter;
    }
    

    4 xhr

    module.exports = function xhrAdapter(config) {
      return new Promise(function dispatchXhrRequest(resolve, reject) {
        var requestData = config.data;
        var requestHeaders = config.headers;
    
    
        if (utils.isFormData(requestData)) {
          delete requestHeaders['Content-Type']; // Let the browser set it
        }
    
        var request = new XMLHttpRequest();
    
        // HTTP basic authentication
        if (config.auth) {
          var username = config.auth.username || '';
          var password = config.auth.password ? unescape(encodeURIComponent(config.auth.password)) : '';
          requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password);
        }
    
        // 合并请求连接,可以通过{baseURL: 'xxx'}或者axios.defaults.baseURL = 'https://xxx';设置
        var fullPath = buildFullPath(config.baseURL, config.url);
        request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);
    
        // Set the request timeout in MS
        request.timeout = config.timeout;
    
        // Listen for ready state
        // // onreadystatechange的状态码
        // 0: 初始化, XMLHttpRequest对象还没有完成初始化
        // 1: 载入, XMLHttpRequest对象开始发送请求
        // 2: 载入完成, XMLHttpRequest对象的请求发送完成
        // 3: 解析, XMLHttpRequest对象开始读取服务器的响应
        // 4: 完成, XMLHttpRequest对象读取服务器响应结束
        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;
          }
    
          // Prepare the response
          var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;
          var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response;
          var response = {
            data: responseData,
            status: request.status,
            statusText: request.statusText,
            headers: responseHeaders,
            config: config,
            request: request
          };
          // 当返回的status状态码在[200, 300)之间,返回resolve(response)
          //default.js中 validateStatus: function validateStatus(status) {
          // return status >= 200 && status < 300;
          // }
          settle(resolve, reject, response);
    
          // Clean up request
          request = null;
        };
    
        // Handle browser request cancellation (as opposed to a manual cancellation)
        // 取消请求
        request.onabort = function handleAbort() {
          if (!request) {
            return;
          }
    
          reject(createError('Request aborted', config, 'ECONNABORTED', request));
    
          // Clean up request
          request = null;
        };
    
        // 处理网络错误问题
        request.onerror = function handleError() {
          // Real errors are hidden from us by the browser
          // onerror should only fire if it's a network error
          reject(createError('Network Error', config, null, request));
    
          // Clean up request
          request = null;
        };
    
        // 处理请求超时问题。
        request.ontimeout = function handleTimeout() {
          var timeoutErrorMessage = 'timeout of ' + config.timeout + 'ms exceeded';
          if (config.timeoutErrorMessage) {
            timeoutErrorMessage = config.timeoutErrorMessage;
          }
          reject(createError(timeoutErrorMessage, config, 'ECONNABORTED',
            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 (utils.isStandardBrowserEnv()) {
          // Add xsrf header
          var xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath)) && config.xsrfCookieName ?
            cookies.read(config.xsrfCookieName) :
            undefined;
    
          if (xsrfValue) {
            requestHeaders[config.xsrfHeaderName] = xsrfValue;
          }
        }
    
        // Add headers to the request
        if ('setRequestHeader' in request) {
          utils.forEach(requestHeaders, function setRequestHeader(val, key) {
            if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') {
              // Remove Content-Type if data is undefined
              delete requestHeaders[key];
            } else {
              // Otherwise add header to the request
              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 (config.responseType) {
          try {
            request.responseType = config.responseType;
          } catch (e) {
            // Expected DOMException thrown by browsers not compatible XMLHttpRequest Level 2.
            // But, this can be suppressed for 'json' type as it can be parsed by default 'transformResponse' function.
            if (config.responseType !== 'json') {
              throw e;
            }
          }
        }
    
        // Handle progress if needed
        if (typeof config.onDownloadProgress === 'function') {
          request.addEventListener('progress', config.onDownloadProgress);
        }
    
        // Not all browsers support upload events
        if (typeof config.onUploadProgress === 'function' && request.upload) {
          request.upload.addEventListener('progress', config.onUploadProgress);
        }
        // 取消请求。
        if (config.cancelToken) {
          // Handle cancellation
          config.cancelToken.promise.then(function onCanceled(cancel) {
            if (!request) {
              return;
            }
    
            request.abort();
            reject(cancel);
            // Clean up request
            request = null;
          });
        }
    
        if (!requestData) {
          requestData = null;
        }
    
        // Send the request
        request.send(requestData);
      });
    };
    
    1. 创建new XMLHttpRequest()实例

    2. 合并请求连接,可以通过{baseURL: 'xxx'}或者axios.defaults.baseURL = 'https://xxx';设置

    3. 监听onreadystatechange变化,

      • 0: 初始化, XMLHttpRequest对象还没有完成初始化

      • 1: 载入, XMLHttpRequest对象开始发送请求

      • 2: 载入完成, XMLHttpRequest对象的请求发送完成

      • 3: 解析, XMLHttpRequest对象开始读取服务器的响应

      • 4: 完成, XMLHttpRequest对象读取服务器响应结束

    4. 收集response返回数据,执行settle函数,调用validateStatus(status)判断是否在[200, 300),成功状态后resolve(response)返回数据。

    结合代码

    <!DOCTYPE html>
    <html>
    
    <head>
      <title>axios</title>
    </head>
    
    <body>
      <div>
        <button onclick="handleGet()">get</button>
        <button onclick="handlePost()">post</button>
      </div>
      <script src="../源码/axios/dist/axios.js"></script>
      <script>
        axios.defaults.baseURL = 'http://localhost:8081';
        axios.interceptors.request.use((config) => {
          console.log('request1')
          return config
        })
        axios.interceptors.request.use((config) => {
          console.log('request2')
          return config
        })
        axios.interceptors.response.use((response) => {
          console.log('response1')
          return response
        })
        axios.interceptors.response.use((response) => {
          console.log('response2')
          return response
        })
        function handleGet(params) {
          axios.get('/list').then(json => {
            console.log(json);
          })
        }
        function handlePost(params) {
          console.log('handlePost')
        }
    
        // 返回的顺序
        // request2
        // request1
        // response1
        // response2
        // {data: {…}, status: 200, statusText: "OK", headers: {…}, config: {…}, …}
      </script>
    </body>
    
    </html>
    

    5 取消请求

    function CancelToken(executor) {
      if (typeof executor !== 'function') {
        throw new TypeError('executor must be a function.');
      }
    
      var resolvePromise;
      // 创建了内置的promise对象,将resovle控制放在executor中去控制
      //eg: let resolveHandle;
      // new Promise((resolve) => {
      //   resolveHandle = resolve;
      // }).then((val) => {
      //   console.log('resolve', val);
      // });
      // resolveHandle('ok');
      this.promise = new Promise(function promiseExecutor(resolve) {
        resolvePromise = resolve;
      });
    
      var token = this;
      executor(function cancel(message) {
        if (token.reason) {
          // Cancellation has already been requested
          return;
        }
    
        token.reason = new Cancel(message);
        resolvePromise(token.reason);
      });
    }
    
    1. cancelToken主要做了什么事情,内置创建了promise对象,通过resolvePromise=resolve,将控制放在executor中去控制,config.cancelToken.promise.then在异步中去取消请求
    if (config.cancelToken) {
          // Handle cancellation
          // abort方法只会对未进行相应的请求进行取消,已响应的请求执行了也不会有什么作用
          config.cancelToken.promise.then(function onCanceled(cancel) {
            if (!request) {
              return;
            }
    
            request.abort();
            reject(cancel);
            // Clean up request
            request = null;
          });
        }
    
    1. cancelToken只会对未进行相应的请求进行取消,已响应的请求执行了也不会有什么作用。
        let CancelToken = axios.CancelToken;
        let map = new Map()
        axios.defaults.baseURL = 'http://localhost:8081';
        axios.interceptors.request.use((config) => {
          const f = map.get(config.url)
          if (f) {
            f()
            map.delete(config.url)
          }
          config.cancelToken = new CancelToken(function executor(c) {
            map.set(config.url, c)
          })
          return config
        })
        axios.interceptors.response.use((response) => {
          const f = map.get(config.url)
          if (f) {
            map.delete(config.url)
          }
          return response
        })
    
        function handleGet1(params) {
          axios.get('/cancel-list1').then(json => {
            console.log(json)
          });
        }
    
        function handleGet2(params) {
          axios.get('/cancel-list2').then(json => {
            console.log(json)
          });
        }
    
        function handlePost(params) {
          console.log('handlePost')
        }
    
        function handleCancel() {
          console.log(123);
          cancel()
        }
    

    总结

    1. axios是一个基于promiseHttp库,在浏览器环境使用XHR,在node环境中使用http模块发送请求,在axios,可以对请求,返回进行拦截,支持链式拦截,请求拦截为倒序拦截,使用unshift往前插入数据,返回拦截使用push方法,正常拦截。

    2. 取消请求的是一个异步分离的设计方案,利用promise的异步效果,通过切换promise的状态,从而达到异步取消请求的实现。

    3. axiosaxios.create的差别是增加了cancelToken等方法。


    起源地下载网 » 初探axios源码

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元