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

    正文概述 掘金(Mengxixi)   2021-05-30   565

    写在前面

    本文章是axios学习源码的笔记文章,原视频是尚硅谷的axios教学视频,链接如下:www.bilibili.com/video/BV1NJ…

    axios与Axios的关系

    1. Axios是构造函数,axios是一个函数
    2. 语法上来说axios不是由Axios构建的实例,但是功能上来说axios具有Axios实例的功能(属性和方法)
    3. axios是Axios.prototype.request函数bind()返回的函数

    图解:

    axios源码分析——学习笔记

    axios与 axios.create返回的instance区别

    1. axios多了create,CancelToken,all等方法
    2. axios和instance的配置(axios.defaults+createConfig)可能不一样

    axios.js代码段:

    function createInstance(defaultConfig) {
      //创建Axios实例对象context
      var context = new Axios(defaultConfig);
      //将Axios.prototype.request函数的this指向绑定context,
      //并将该新函数返回赋值给instance
      debugger
      var instance = bind(Axios.prototype.request, context);
    
      //将原型上的方法添加到instance上
      utils.extend(instance, Axios.prototype, context);
    
      // 将context(Axios原型实例)上的属性(defaults和interceptors)添加到instance上
      utils.extend(instance, context);
    
      return instance;
    }
    
    // 创建axios用于模块输出(调用了createInstance)
    var axios = createInstance(defaults);
    
    // Expose Axios class to allow class inheritance
    //将Axios类暴露,从而实现类的继承
    axios.Axios = Axios;
    
    // 定义create方法
    axios.create = function create(instanceConfig) {
      return createInstance(mergeConfig(axios.defaults, instanceConfig));
    };
    
    // Expose Cancel & CancelToken
    axios.Cancel = require('./cancel/Cancel');
    axios.CancelToken = require('./cancel/CancelToken');
    axios.isCancel = require('./cancel/isCancel');
    
    // Expose all/spread
    axios.all = function all(promises) {
      return Promise.all(promises);
    };
    axios.spread = require('./helpers/spread');
    
    // Expose isAxiosError
    axios.isAxiosError = require('./helpers/isAxiosError');
    

    axios运行的整体流程

    axios源码分析——学习笔记 从图中可以看出axios运行时最重要的三个函数:request(config) => dispatchRequest(config) => xhrAdapter(config)

    request

    代码段:

    Axios.prototype.request = function request(config) {
      /*eslint no-param-reassign:0*/
      // Allow for axios('example/url'[, config]) a la fetch API
      if (typeof config === 'string') {
        config = arguments[1] || {};
        config.url = arguments[0];
      } else {
        config = config || {};
      }
    
      config = mergeConfig(this.defaults, config);
    
      // Set config.method
      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);
    
      this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
        chain.unshift(interceptor.fulfilled, interceptor.rejected);
      });
    
      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;
    };
    

    request函数的代码最主要的部分如下,这是request函数逻辑的重要实现部分:

      var chain = [dispatchRequest, undefined];
      var promise = Promise.resolve(config);
    
      this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
        chain.unshift(interceptor.fulfilled, interceptor.rejected);
      });
    
      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;
    

    这段代码首先定义了一个chain数组,通过数组的unshift方法和push方法,分别将请求拦截器的回调函数推入chain的左边,将响应拦截器的回调函数推入chain的右边,而chain的中间是dispatchRequest函数。之后通过从左往右弹出chain数组元素进行promise链式调用完成了从请求拦截器到响应拦截器之间的工作。 举一个例子,我们准备了两个请求拦截器requestInt1和requestInt2,两个响应拦截器responseInt1和responseInt2,用图来解释这四个拦截器是如何放入chain数组中以及最后如何被链式调用的。

    axios源码分析——学习笔记 由图可知,由于promise链式调用是两两一组(拦截器的成功回调+失败回调),所以chain数组初始定义了一个undefined和dispatchRequest组合防止链式调用时乱序。

    dispatchRequest

    dispatchRequest模块的流程

    转换请求数据 => 调用xhrAdapter()发请求 => 请求返回后转换响应数据并返回promise对象

    转换请求数据

      // Transform request data
      config.data = transformData(
        config.data,
        config.headers,
        config.transformRequest
      );
     // Flatten headers
      config.headers = utils.merge(
        config.headers.common || {},
        config.headers[config.method] || {},
        config.headers
      );
    
    

    该部分主要用于转换的函数为defaults模块的transformRequest函数,代码如下:

    transformRequest: [function transformRequest(data, headers) {
        normalizeHeaderName(headers, 'Accept');
        normalizeHeaderName(headers, 'Content-Type');
        if (utils.isFormData(data) ||
          utils.isArrayBuffer(data) ||
          utils.isBuffer(data) ||
          utils.isStream(data) ||
          utils.isFile(data) ||
          utils.isBlob(data)
        ) {
          return data;
        }
        if (utils.isArrayBufferView(data)) {
          return data.buffer;
        }
        if (utils.isURLSearchParams(data)) {
          setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');
          return data.toString();
        }
        if (utils.isObject(data)) {
          setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
          return JSON.stringify(data);
        }
        return data;
      }]
    

    请求返回后转换响应数据

      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;
      }
    

    这部分的代码在adapter调用之后的promise回调中,用于转换的函数是defaults模块的transformResponse函数。

      transformResponse: [function transformResponse(data) {
        /*eslint no-param-reassign:0*/
        if (typeof data === 'string') {
          try {
            data = JSON.parse(data);
          } catch (e) { /* Ignore */ }
        }
        return data;
      }]
    

    一开始作者不理解这一块的逻辑,后来试了一下,发现JSON数据的类型就是string格式,如果data是一个非JSON格式的字符串,则这段会报错,因此该函数选择了try cache的结构来防止程序报错。

    xhrAdapter

    xhrAdapter模块的流程

    创建XHR对象(最底层,调用AJAX),根据config进行相应设置,发送特定请求,接受相应数据,返回promise。xhrAdapter模块的主要代码如下(从xhr.open到onreadystatechange部分):

    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);
        }
    
        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
        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
          };
    
          settle(resolve, reject, response);
    
          // Clean up request
          request = null;
        };
    

    可以看到该部分返回一个Promise对象,而该对象状态的判定交给了settle函数,该部分函数代码如下:

    module.exports = function settle(resolve, reject, response) {
      var validateStatus = response.config.validateStatus;
      if (!response.status || !validateStatus || validateStatus(response.status)) {
        resolve(response);
      } else {
        reject(createError(
          'Request failed with status code ' + response.status,
          response.config,
          null,
          response.request,
          response
        ));
      }
    };
    

    该部分的validateStatus是axios的config设定,定义了返回状态码的合法范围,默认范围为[200,299]。

    取消请求

    axios取消请求的实现牵涉到config中的canceltoken的配置,先来看看canceltoken的用法(cancel函数可以传入参数--取消原因):

    const CancelToken = axios.CancelToken;
    let cancel;
    
    axios.get('/user/12345', {
      cancelToken: new CancelToken(function executor(c) {
        // An executor function receives a cancel function as a parameter
        cancel = c;
      })
    });
    
    // cancel the request
    cancel();
    
    

    CancelTOken部分的源码如下:

    function CancelToken(executor) {
      if (typeof executor !== 'function') {
        throw new TypeError('executor must be a function.');
      }
      //为取消请求准备了一个Promise对象promise,并把该对象的resolve函数存到外面
      var resolvePromise;
      this.promise = new Promise(function promiseExecutor(resolve) {
        resolvePromise = resolve;
      });
      //保存当前的token对象
      var token = this;
      executor(function cancel(message) {
        //如果token中已有reason属性,说明取消已经被请求过
        if (token.reason) {
          // Cancellation has already been requested
          return;
        }
        //token.reason指定为一个Cancel对象
        token.reason = new Cancel(message);
        resolvePromise(token.reason);
      });
    }
    

    利用executor函数的参数,这里很巧妙地将CancelToken内部定义好的cancel函数传递给外部调用者,在需要取消请求时调用cancel函数,使得resolvePromise的状态变为fulfilled,值为reason。 而CancelToken的实例对象的canceltoken的promisethen方法的调用写在了xhr.js里面:

        if (config.cancelToken) {
          // Handle cancellation
          config.cancelToken.promise.then(function onCanceled(cancel) {
            if (!request) {
              return;
            }
            //取消请求
            request.abort();
            //让请求的promise失败
            reject(cancel);
            // Clean up request
            request = null;
          });
        }
    

    需要注意的是,该部分的cancel不是之前的cancel函数,而是一个Cancel对象,是CancelToken.js模块的token.reason。 调用cancel()的具体细节如下:

    axios源码分析——学习笔记


    起源地下载网 » axios源码分析——学习笔记

    常见问题FAQ

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

    发表评论

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

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

    联系作者

    请选择支付方式

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