cover_image

axios的使用与源码

兰厅 新东方技术
2023年11月17日 09:19

1

axios介绍


       Axios由 Matt Zabriskie 在 2014 年创建,旨在解决基于 Promise 的 HTTP 请求问题,可以作用于node.js 和浏览器中。在服务端它使用原生node.js 的http模块, 在浏览端则使用 XMLHttpRequest。Axios提供了许多高级功能,如拦截请求和响应,转换请求和响应数据,取消请求,自动转换 JSON 数据,客户端支持防止 CSRF/XSRF等。

2

axios的特性


2.1支持promise API

axios.get('/user?ID=12345')  .then(function (response) {    // 处理成功情况    console.log(response);  })  .catch(function (error) {    // 处理错误情况    console.log(error);  });


2.2拦截请求和响应

      axios的拦截器可以在请求或响应被then或catch处理前拦截它们。

请求拦截器:

axios.interceptors.request.use(function (config) {    // 在发送请求之前做些什么    return config;  }, function (error) {    // 对请求错误做些什么    return Promise.reject(error);  });

响应拦截器:

axios.interceptors.response.use(function (response) {    // 2xx 范围内的状态码都会触发该函数。    // 对响应数据做点什么    return response;  }, function (error) {    // 超出 2xx 范围的状态码都会触发该函数。    // 对响应错误做点什么    return Promise.reject(error);  });

      如果有多个请求拦截,会先执行最后一个,依次向前面执行。如果有多个响应拦截,会先执行第一个,依次向后执行。如下图所示:

图片


2.3全局配置

    可以指定axios的全局配置,它将作用于每个请求。

axios.defaults.baseURL = 'https://api.example.com';axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';


2.4取消请求

      一般用于发送重复请求的情况。从v0.22.0 版本开始,Axios 支持fetch API方式——AbortController 取消请求,CancelToken已被弃用。

const controller = new AbortController(); axios.get('/foo/bar', {   signal: controller.signal}).then(function(response) {   //...});// 取消请求controller.abort()

3

axios的请求方式


3.1axios()

// 发起一个post请求axios({  method: 'post',  url: '/user/12345',  data: {    firstName: 'Fred',    lastName: 'Flintstone'  }});

3.2调用API

axios.request(config)axios.get(url[, config])axios.delete(url[, config])axios.head(url[, config])axios.options(url[, config])axios.post(url[, data[, config]])axios.put(url[, data[, config]])axios.patch(url[, data[, config]])

3.3创建实例

const instance = axios.create({  baseURL: 'https://some-domain.com/api/',  timeout: 1000,  headers: {'X-Custom-Header': 'foobar'}});

4

axios源码分析


axios源码结构:

图片

(1)入口文件

// 创建axios实例function createInstance(defaultConfig) {  const context = new Axios(defaultConfig);  // 将Axios的实例方法request赋值给instance,目前instance就是request方法  const instance = bind(Axios.prototype.request, context);  // 在instance上添加Axios所有的原型属性,这个做可以使axios既当方法调用,又可当对象调用  utils.extend(instance, Axios.prototype, context, {allOwnKeys: true});  // 在instance上添加context所有的属性  utils.extend(instance, context, null, {allOwnKeys: true});  // 在instance上添加create方法  instance.create = function create(instanceConfig) {    return createInstance(mergeConfig(defaultConfig, instanceConfig));  };   return instance;} // 最终要导出的实例const axios = createInstance(defaults); // 向外暴露Axios类用于继承(使用较少)axios.Axios = Axios; // 中断请求的相关方法axios.CanceledError = CanceledError;axios.CancelToken = CancelToken; // 错误处理axios.AxiosError = AxiosError; // 检测是否为axios抛出的错误axios.isAxiosError = isAxiosError; export default axios

入口文件的createInstance方法主要做了三件事:

     a. 创建instance变量,挂载上request方法。

     b. 将Axios所有的原型方法添加到instance上。

     c. 将Axios实例context上所有的属性添加到instance。

我们最终使用的axios就是导出的instance变量。

(2)axios核心类

class Axios {  constructor(instanceConfig) {    // axios的配置    this.defaults = instanceConfig;    // 拦截器    this.interceptors = {      request: new InterceptorManager(), // 请求拦截器      response: new InterceptorManager() // 响应拦截器    };  }   // 发送请求的方法  request(configOrUrl, config) {    ...  }} // 将普通请求(无body数据)挂载到Axios的prototype上utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {  Axios.prototype[method] = function(url, config) {    return this.request(mergeConfig(config || {}, {      method,      url,      data: (config || {}).data    }));  };}); // 将有body数据的请求挂载到Axios的prototype上utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {  function generateHTTPMethod(isForm) {    return function httpMethod(url, data, config) {      // 最终调用request()      return this.request(mergeConfig(config || {}, {        method,        headers: isForm ? {          'Content-Type': 'multipart/form-data'        } : {},        url,        data      }));    };  }}); export default Axios;

      所有的请求方法,最终都会调用request方法,这个方法是axios请求的核心,下面详细看一下request方法:

// 发送请求的方法request(configOrUrl, config) {  // 判断参数类型,以支持不同的请求形式  if (typeof configOrUrl === 'string') {    // 第一个参数是字符串的情况 axios('url', {})    config = config || {};    config.url = configOrUrl;  } else {    // 传入的是对象    config = configOrUrl || {};  }  // 合并默认配置  config = mergeConfig(this.defaults, config);   // 请求方法转化为小写  config.method = (config.method || this.defaults.method || 'get').toLowerCase();  config.headers = AxiosHeaders.concat(contextHeaders, headers);   // 请求拦截器存储数组  const requestInterceptorChain = [];   let synchronousRequestInterceptors = true;  // 调用拦截器的forEach方法(高阶函数)  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {    // runWhen条件执行(当拦截器需要条件执行的时候,只需要在use的时候配置好runWhen就行)    if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {      return;    }    // 标识拦截器是同步还是异步,默认是flase 异步,只有所有请求拦截器都是同步的情况下才会同步进行,    synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;     requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);  });  // 响应拦截器存储数组  const responseInterceptorChain = [];  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {    responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);  });   let promise;  let i = 0;  let len;  // 异步(默认情况)  if (!synchronousRequestInterceptors) {    // 下面是axios经典的promise链式调用    const chain = [dispatchRequest.bind(this), undefined];    // 将请求拦截器里的方法添加到数组前面    chain.unshift.apply(chain, requestInterceptorChain); // 等同于chain.unshift(...requestInterceptorChain),但扩展运算符是深拷贝,会占用内存空间    // 将响应拦截器里的方法添加到数组后面    chain.push.apply(chain, responseInterceptorChain);    len = chain.length;     promise = Promise.resolve(config);    // 执行chain数组中的函数    while (i < len) {      promise = promise.then(chain[i++], chain[i++]);    }     return promise;  }  // 以下是同步的逻辑  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 {    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;}

主要逻辑如下图所示:

图片

(3)拦截器实现

class InterceptorManager {  constructor() {    this.handlers = [];  }  // use方法添加拦截器  use(fulfilled, rejected, options) {    // 将拦截器成功、失败的回调,以及设置的options参数添加到this.handlers数组    this.handlers.push({      fulfilled,      rejected,      synchronous: options ? options.synchronous : false,      runWhen: options ? options.runWhen : null    });    return this.handlers.length - 1;  }  // 注销指定拦截器  eject(id) {    if (this.handlers[id]) {      // 将数组中该拦截器设置为null      this.handlers[id] = null;    }  }  // 注销所有拦截器  clear() {    if (this.handlers) {      this.handlers = [];    }  }  // 遍历拦截器  forEach(fn) {    utils.forEach(this.handlers, function forEachHandler(h) {      if (h !== null) {        fn(h);      }    });  }}

(4)dispatchRequest方法

      dispatchRequest是axios发送请求的核心方法,返回adapter适配器,adapter会根据当前环境,返回xhr或http方法,最终的请求方法由xhr或http方法执行。

import adapters from "../adapters/adapters.js";export default function dispatchRequest(config) {    // 适配器模式    const adapter = adapters.getAdapter(config.adapter || defaults.adapter);    return adapter(config).then(function onAdapterResolution(response) {    // 转换响应数据    response.data = transformData.call(      config,      config.transformResponse,      response    );     response.headers = AxiosHeaders.from(response.headers);     return response;  }, function onAdapterRejection(reason) {    ...    return Promise.reject(reason);  });}

       getAdapter只做了一件事,就是根据不同的环境,使用不同的请求方式,如果用户自定义了adapter就使用config.adapter,否则就使用默认的defaults.adapter,代码如下:

import httpAdapter from './http.js';import xhrAdapter from './xhr.js'; const knownAdapters = {  http: httpAdapter,  xhr: xhrAdapter}
export default {  // 参数adapters是用户自定义的参数,可以是xhr或http  getAdapter: (adapters) => {    adapters = utils.isArray(adapters) ? adapters : [adapters];    const {length} = adapters;    let nameOrAdapter;    let adapter;    // 遍历adapters    for (let i = 0; i < length; i++) {      nameOrAdapter = adapters[i];      // 给adapter赋值      if((adapter = utils.isString(nameOrAdapter) ? knownAdapters[nameOrAdapter.toLowerCase()] : nameOrAdapter)) {        break; // 终止循环      }    }    // 最终返回knownAdapters中的xhrAdapter或httpAdapter    return adapter;  }}

      xhrAdapter中封装了原生的ajax请求,httpAdapter中封装了node的http模块,浏览器端用ajax请求,服务端用的是node的http模块。我们日常在浏览器端使用较多,因此着重看一下ajax的封装:

export default isXHRAdapterSupported && function (config) {  return new Promise(function dispatchXhrRequest(resolve, reject) {     ...     // 创建XMLHttpRequest对象    let request = new XMLHttpRequest();     // HTTP基本认证    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));    }     // 设置请求方式    request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);     // 设置超时时间    request.timeout = config.timeout;           if ('onloadend' in request) {      request.onloadend = onloadend;    } else {      // 监听readyState属性变化      request.onreadystatechange = function handleLoad() {        ...      };    }     // 设置请求头    if ('setRequestHeader' in request) {      utils.forEach(requestHeaders.toJSON(), function setRequestHeader(val, key) {        request.setRequestHeader(key, val);      });    }     // 指跨域时是否携带cookie,默认为false    if (!utils.isUndefined(config.withCredentials)) {      request.withCredentials = !!config.withCredentials;    }     // 如果配置了download或upload,需要监听progress事件,处理上传或下载的应用    if (typeof config.onDownloadProgress === 'function') {      request.addEventListener('progress', progressEventReducer(config.onDownloadProgress, true));    }     if (typeof config.onUploadProgress === 'function' && request.upload) {      request.upload.addEventListener('progress', progressEventReducer(config.onUploadProgress));    }     // 发送请求    request.send(requestData || null);  });}

      以上就是 axios的源码的核心内容,当然还有很多细节没有说到,比如:错误处理,状态码处理等,大家有兴趣的可以自己去细读源码,只有自己阅读一次才能更好的理解 axios的优雅之处。

继续滑动看下一个
新东方技术
向上滑动看下一个