axios(九)-- 适配器(adapter)处理请求

陆阳曜
2023-12-01

Axios 中的公共方法

1、关键

adapter 做了一件事非常简单,就是根据不同的环境 使用不同的请求。
如果用户自定义了adapter,就用config.adapter。
否则就是默认是default.adpter.
adpter 适配浏览器的xhr,和node的http

2、源码:默认适配器 default.js

import utils from './utils'

var defaults = {
    // ...
    // 请求超时时间,默认不超时
    timeout: 0,
    // 转换请求
    transformRequest: [function transformRequest(data, headers) {
        // 判断 data
        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();
        }
      
          // 如果 data 是对象,或 Content-Type 设置为 application/json
        if (utils.isObject(data) || (headers && headers['Content-Type'] === 'application/json')) {
            // 设置 Content-Type 为 application/json
            setContentTypeIfUnset(headers, 'application/json');
            // 将 data 转换为 json 字符串返回
            return JSON.stringify(data);
        }
    
          return data;
    }],
      
    // 转换响应数据
    transformResponse: [function transformResponse(data) {
      // ...
      // if (strictJSONParsing || (forcedJSONParsing && utils.isString(data) && data.length)) {
      if (typeof data === 'string') {
        try {
          // 将 data 转换为 json 对象并返回
          return JSON.parse(data);
        } catch (e) {
          // ...
        }
      }
      return data;
    }],

    // 判断响应状态码的合法性: [200, 299]
    validateStatus: function validateStatus(status) {
      return status >= 200 && status < 300;
    },

    adapter: getDefaultAdapter()
}

function getDefaultAdapter () {
  var adapter;

  // 判断当前环境
  if (typeof XMLHttpRequest !== 'undefined') {
    // 浏览器
    adapter = require('./adapters/xhr')
  } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
    // NODE环境,使用http模块
    adapter = require('./adapters/http')
  }

  return adapter
}

adapters/xhr.js

基于promise 的xhr请求

module.exports = function xhrAdapter (config) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest()

         // 设置完整路径,拼接url,例如:https://www.baidu,com + /api/test
        var fullPath = buildFullPath(config.baseURL, config.url);
        // 初始化一个请求,拼接url,例如:https://www.baidu,com/api/test + ?a=10&b=20
        xhr.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);
        
        // 超时断开,默认 0 永不超时
        xhr.timeout = config.timeout;
        
        xhr.onreadystatechange = function() {
          // request不存在或请求状态不是4, 直接结束
          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;
          }
    
          // 准备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
        }
        // 根据响应状态码来确定请求的promise的结果状态(成功/失败)
        settle(resolve, reject, response);

        // 将请求对象赋空
        request = null;
      };

        // 超时触发该事件
        xhr.ontimeout =  function handleTimeout() {
      		reject(createError('timeout of ' + config.timeout + 'ms exceeded', config,'ECONNABORTED',request));

      // Clean up request
      request = null;
    };;
        // 取消请求触发该事件
        xhr.onabort = function handleAbort() {
	      if (!request) {
	        return;
	      }
	      // reject promise, 指定aborted的error
	      reject(createError('Request aborted', config, 'ECONNABORTED', request));
	
	      // Clean up request
	      request = null;
	    };
        // 一般是网络问题触发该事件
        xhr.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;
		    };
		;
        
        // 标准浏览器(有 window 和 document 对象)
        if (utils.isStandardBrowserEnv()) {
          // 非同源请求,需要设置 withCredentials = true,才会带上 cookie
          var xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath)) && config.xsrfCookieName ?
            cookies.read(config.xsrfCookieName) :
            undefined;
          if (xsrfValue) {
            requestHeaders[config.xsrfHeaderName] = xsrfValue;
          }
        }
        // request对象携带 headers 去请求
        if ('setRequestHeader' in xhr) {
          utils.forEach(requestHeaders, function setRequestHeader(val, key) {
            if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') {
              // data 为 undefined 时,移除 content-type,即不是 post/put/patch 等请求
              delete requestHeaders[key];
            } else {
              xhr.setRequestHeader(key, val);
            }
          });
        }

        // ...,取消请求的

        // 发送请求
        xhr.send(requestData)
    })
}

settle.js

'use strict';

var createError = require('./createError');

/**
 * 根据响应状态码来确定是成功还是失败
 * 
 * Resolve or reject a Promise based on response status.
 *
 * @param {Function} resolve A function that resolves the promise.
 * @param {Function} reject A function that rejects the promise.
 * @param {object} response The response.
 */
module.exports = function settle(resolve, reject, response) {
  // 判断响应状态码的合法性
  var validateStatus = response.config.validateStatus;
  
  if (!validateStatus || validateStatus(response.status)) {
    resolve(response);
  } else {
    reject(createError(
      'Request failed with status code ' + response.status,
      response.config,
      null,
      response.request,
      response
    ));
  }
};

createError.js

'use strict';

var enhanceError = require('./enhanceError');

/**
 * 创建error对象的函数
 * 
 * Create an Error with the specified message, config, error code, request and response.
 *
 * @param {string} message The error message.
 * @param {Object} config The config.
 * @param {string} [code] The error code (for example, 'ECONNABORTED').
 * @param {Object} [request] The request.
 * @param {Object} [response] The response.
 * @returns {Error} The created error.
 */
module.exports = function createError(message, config, code, request, response) {
  var error = new Error(message);
  return enhanceError(error, config, code, request, response);
};

buildURL.js

'use strict';

var utils = require('./../utils');
// 替换
function encode(val) {
  return encodeURIComponent(val).
    replace(/%40/gi, '@').
    replace(/%3A/gi, ':').
    replace(/%24/g, '$').
    replace(/%2C/gi, ',').
    replace(/%20/g, '+').
    replace(/%5B/gi, '[').
    replace(/%5D/gi, ']');
}
/**
 * 构建一个带参数的url
 * 
 * Build a URL by appending params to the end
 *
 * @param {string} url The base of the url (e.g., http://www.google.com)
 * @param {object} [params] The params to be appended
 * @returns {string} The formatted url
 */
 module.exports = function buildURL(url, params, paramsSerializer) {
    /*eslint no-param-reassign:0*/
    if (!params) {
      return url;
    }
  
    var serializedParams;
    if (paramsSerializer) {
      serializedParams = paramsSerializer(params);
    } else if (utils.isURLSearchParams(params)) {
      serializedParams = params.toString();
    } else {
      var parts = [];
  
      utils.forEach(params, function serialize(val, key) {
        if (val === null || typeof val === 'undefined') {
          return;
        }
  
        if (utils.isArray(val)) {
          key = key + '[]';
        } else {
          val = [val];
        }
  
        utils.forEach(val, function parseValue(v) {
          if (utils.isDate(v)) {
            v = v.toISOString();
          } else if (utils.isObject(v)) {
            v = JSON.stringify(v);
          }
          parts.push(encode(key) + '=' + encode(v));
        });
      });
  
      serializedParams = parts.join('&');
    }
  
    if (serializedParams) {
      var hashmarkIndex = url.indexOf('#');
      if (hashmarkIndex !== -1) {
        url = url.slice(0, hashmarkIndex);
      }
  
      url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams;
    }
  
    return url;
  };
  

3、扩展:自定义 adapter

通过上面对 adapter 的分析,可以发现如果自定义 adapter 的话,是可以接管 axios 的请求和响应数据的,因此可以自定义 adapter 实现 mock;

const mockUrl = {
    '/mock': {data: xxx}
};

const instance = Axios.create({
    adapter: (config) => {
        if (!mockUrl[config.url]) {
            // 调用默认的适配器处理需要删除自定义适配器,否则会死循环
            delete config.adapter
            return Axios(config)
        }
        return new Promise((resolve, reject) => {
            resolve({
                data: mockUrl[config.url],
                status: 200,
            })
        })
    }
})
 类似资料: