axios源码分析

一、前言

1.特点

  • 在浏览器端借助axios向服务端发送ajax请求。
  • 在nodejs中借助axios向服务端发送http请求
  • 支持Promise

2.使用

在项目中一般使用包管理工具npm、yarn来安装相关依赖。而在一般的练习环节就使用script标签引入就行。

如果响应过慢的话,使用BootCDN 来使用资源

www.bootcdn.cn/

  • 关于请求的链接

用mock或者json-server模拟一下接口数据,能方便的去更好的练习

  • 关于调试

在 gitee上这个文件中node_modules/axios/dist/mine-axios.js,是进行打包处理了的,直接通过script引入来进行相关的调试

详细源码分析,关键地方都做了注释的,可以查看node_modules/axios/lib文件夹

二、基本使用

可以使用axios基本方法来发送请求,也可以使用它的方法

1.axios()

  • axios({url,config})函数接收一个对象 ,包括请求地址和相关参数。
  • 返回一个promise对象
axios({
        method:'GET',
        url:'http://localhost:3000/posts/3',
        //设置请求体
      	data:{
      	
      	}
      })
      .then(res=>{
        console.log(res)
      })

复制代码

更多的使用方法在axios官网去看相关文档

2.配置对象解析

  • url

指明请求的地址,必需的

  • method

指明请求的方法,如果没有指定method,那么请求默认使用get方法

  • baseURL

设置请求链接的基础结构,它会和url进行一个结合

  • data

作为请求主体被发送的数据,适用在post、put、patch这几个请求方法中

  • timeout

指定请求超时的毫秒数,如果超过指定时间,请求就会被中断

  • transformRequest

允许在向服务器发送前,修改请求数据。只能用在post、put、patch这几个请求方法中,

  transformRequest: [function (data) {
    // 对 data 进行任意转换处理

    return ;
  }],
  
复制代码

**注意:**数组中的函数必须返回一个字符串/ArrayBuffer/Stream

  • transformResponse

在响应数据传递给then/catch之前,允许修改响应数据

  • headers

自定义请求头,是一个对象

  • params

和请求一起发送的URL参数,必须是一个无格式对象

params:{
  ID:12345
}
复制代码

3.默认配置

为了简便每次的请求操作,可以设置axios的一个默认配置

axios.defaults加上相关默认参数设置默认配置

axios.defaults.method = 'GET'
复制代码

4.创建实例对象发送请求

利用axios.create()创建实例对象,从而发送请求

const obj = axios.create({
	baseURL:'',
	methods:'GET'
})
obj({
	url:''
})
.then(response=>{

})

复制代码

5.axios拦截器

  • 基本使用

拦截器注主要是函数,分为两大类(请求、响应拦截器)。

请求拦截器:它的主要任务就是在请求被发送之前通过回调函数进行一些操作

响应拦截器: 通过回调函数在数据结果被then、catch接收处理之前,先对数据进行处理

拦截器中主要两个回调函数(成功和失败的回调)—-与promise息息相关.。如果拦截器调用的失败的回调,就像下面例子这样,就返回rejected状态的promise

请求拦截器中:成功的回调函数参数config: 表明我们可以对请求的参数配置对象进行更改。

响应拦截器中:成功的回调函数参数response:它是axios请求默认的返回结果

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

// 2. 添加响应拦截器
axios.interceptors.response.use(function (response) {
    // 对响应数据做点什么
    return  
  }, function (error) {
    // 对响应错误做点什么
    return Promise.reject(error);
  });
复制代码
  • 数据处理顺序

在设置有拦截器的axios请求中,数据首先是先经过请求拦截器——>响应拦截器——>then/catch

  • 多个拦截器执行顺序问题

多个请求拦截器:后定义的先执行

多个响应拦截器:先定义的先执行

具体原因查看axios源码分析

6.取消请求

//2.声明全局变量
let cancel = null;

axios({
	method:'GET',
	url:'',
	//1.axios配置中添加对象的属性
	cancelToken:new axios.CancelToken(function(c){
		//3.将c的值赋给cancel
		cancel = c
	})
})
//4.执行cancel函数
cancel()
复制代码

三、axios请求响应结构

image.png

  • config

包含请求的相关配置信息

image.png

  • data

包含服务器响应的结果

  • headers

响应的头信息

  • request

原生的ajax请求对象——XMLHttpRequest对象

  • status

服务器响应的http状态码

  • statusText

服务器响应的http状态信息

四、axios源码分析

目录文件分析

image.png

详细的源码分析、文件功能在源码中都有注释

axios创建过程

在源码axios.js中可以看到axios的创建,接下来进行一一分析:

var axios = createInstance(defaulyts)
复制代码

axios.js中createInstance(defaults)函数

function createInstance(defaultConfig) {
   //1.Axios.js
    var context = new Axios(defaultConfig);
   //2.bind
    var instance = bind(Axios.prototype.request, context);
   //3.复制
    utils.extend(instance, Axios.prototype, context);
    utils.extend(instance, context);

    return instance;
}

复制代码
1. Axios.js
   var context = new Axios(defaultConfig);

复制代码

这一句代码需要追溯到Axios.js中

function Axios(instanceConfig) {
    this.defaults = instanceConfig;

    this.interceptors = {
        request: new InterceptorManager(),
        response: new InterceptorManager()
    };
}

Axios.prototype.request = function request(config) {
   //...
};

Axios.prototype.getUri = function getUri(config) {
	//...
};
...
//往原型上添加更多的方法


复制代码

在以上代码中主要注意以下几点

  • 设置defaults,配置默认对象
  • 设置interceptors添加请求/响应拦截器

刚好对应之前拦截中的设置方案

axios.interceptors.request.use()
axios.interceptors.response.use()

复制代码
  • 在Axios原型对象上添加方法

在axios实例化对象上就可以调用我们之前使用的axios.request等等许多方法

这也就是为什么我们在使用axios时,可以直接使用它,也可以通过调用相关的方法来使用

**小结:**经过这一步 实例化的对象能够通过调用方法来发送请求等等,但是不能像平常使用的那样直接调用。因此需要进行接下来的步骤

2.bind
var instance = bind(Axios.prototype.request, context);

复制代码

bind是一个绑定函数,在源码中进行的一个封装,将this指向context

这里的instance 与 Axios.prototype.request(发送请求) 代码一致,它是一个函数

3.复制
 utils.extend(instance, Axios.prototype, context);
 utils.extend(instance, context);

复制代码

之前的instance只是一个函数而已。

这里的extend方法是将Axios.prototype和实例化对象context的方法都添加到instance身上。

经过这一步,

  1. instance 是一个函数
  2. 作为对象,具有很多方法
4.模拟实现

在以上过程中简单描述了创建的过程,接下来的话,模拟实现的创建过程会更加清晰一点

    //构造函数
    function Axios(config) {  
      //初始化
      this.defaults = config;
      this.interceptors = {
        request:{},
        response:{}
      }
    }
    //原型添加相关方法
    Axios.prototype.request = function (config) {  
      console.log(config)
    }

    Axios.prototype.get = function (config) {  
      //调用request方法使得可以请求
      return this.request(config);
    }
    Axios.prototype.post = function (config) {  
      //调用request方法使得可以请求
      return this.request(config);
    }

    //声明一个函数
    function createInstance(config) {  
      //1.实例化一个对象
      let context = new Axios(config);//不能当作函数使用
      //2.创建请求函数
      let instance = Axios.prototype.request.bind(context);//可以直接调用request方法
      //3.复制方法(让instance成为一个对象)
      Object.keys(Axios.prototype).forEach(key=>{
        instance[key] = Axios.prototype[key].bind(context);
        //注意这里需要绑定到context上,this指向始终指向context

      })
      //4.为instance添加默认配置(defaults和interceptors)
      Object.keys(context).forEach(key=>{
        instance[key] = context[key];
      })

      return instance;
    }
    var axios = createInstance({method:'get'})
    axios({method:'post'})
    axios.get({data:'beatrix'})

复制代码

axios请求过程

从上面的axios创建过程分析,相信都清楚了axios的调用实质上就是Axios.prototype.request的调用,包括其他原型对象上的方法都是基于request方法的,因此接下来具体分析axios的请求过程

axios({

}).then(res=>{

})

复制代码
1.request
Axios.prototype.request = function request(config) {
//1. 检查传递的参数
    if (typeof config === 'string') {
        config = arguments[1] || {};
        config.url = arguments[0];
    } else {
        config = config || {};
    }
 //2. 将默认配置和用户调用时传入的配置进行合并
    config = mergeConfig(this.defaults, config);

 //3. 对请求方法做判断
    if (config.method) {
        config.method = config.method.toLowerCase();
    } else if (this.defaults.method) {
        config.method = this.defaults.method.toLowerCase();
    } else {
        config.method = 'get';
    }
//4.创建拦截器中间件,undefined做补位
    var chain = [dispatchRequest, undefined];
//5. 创建一个成功的promise  
    var promise = Promise.resolve(config);
//6.遍历请求拦截器和响应拦截器
    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);
    });

//7.依次取出chain的回调函数
//首先是dispatch中
    while (chain.length) {
        promise = promise.then(chain.shift(), chain.shift());
    }
//8.返回一个promise对象
    return promise;
};


复制代码

在第4步中,dispatchRequst是一个函数,用来发送请求的,通过它来调用HTTP、XMLHttprequest。

在第7步中,它将作为promise的成功回调函数被执行

2. dispatchRequest.js
var chain = [dispatchRequest, undefined];

复制代码

在dispatchRequest中,主要做了以下几件事

function dispatchRequest(config) {
    //1.确保头信息存在
    config.headers = config.headers || {};

    //2.对请求数据进行初始化转化
    config.data = transformData(
        config.data,
        config.headers,
        config.transformRequest
    );

    // 3.合并一切其他头信息的配置项
    config.headers = utils.merge(
        config.headers.common || {},
        config.headers[config.method] || {},
        config.headers
    );

    //4.将配置项中关于方法的配置项全部移除
    utils.forEach(
        ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
        function cleanHeaderConfig(method) {
            delete config.headers[method];
        }
    );

    //5. 获取适配器对象 http  xhr
    var adapter = config.adapter || defaults.adapter;

    //6. 发送请求, 返回请求后 promise 对象  ajax HTTP
    return adapter(config).then(function onAdapterResolution(response) {
        throwIfCancellationRequested(config);

        // Transform response data
        response.data = transformData(
            response.data,
            response.headers,
            config.transformResponse
        );
        //设置 promise 成功的值为 响应结果
        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
                );
            }
        }
        //设置 promise 为失败, 失败的值为错误信息
        return Promise.reject(reason);
    });
};


复制代码

**注意:**dispatchRequest函数的返回结果是根据promise.then的返回结果决定的(也就是说它的返回结果同样是一个promise对象)

3.xhr.js

在上述第六步中,发送请求的操作中

return adapter(config).then(function onAdapterResolution(response) {
     
    }, function onAdapterRejection(reason) {
        
});


复制代码

在以上adpter函数中是调用xhr.js中的xhrAdapter函数,作用就是发送ajax请求,返回一个promise对象

4.小结

axios的请求过程实际上就是:::调用Axios.prototype.request函数——>调用dispatchRequest函数——>调用xhrAdapter函数,一步步的返回结果,最终request函数执行完毕,axios也执行完毕,返回一个promise对象

如果看到这里还不是很懂的话,就建议去看源码分析,里面有详细的解释,因为axios请求过程涉及到promise的大量运用,建议把promise多看几遍

5.模拟实现

模拟实现的话,是对上面的功能进行简写,只有核心的结构,如果上面还不是很懂的话,看这个应该可以了。

 //构造函数
     function Axios(config) {  
      //初始化
      this.defaults = config;
      this.interceptors = {
        request:{},
        response:{}
      }
    }
    //1. 原型添加request
    Axios.prototype.request = function (config) {  
      //创建一个promise对象
      let promise = Promise.resolve(config);
      //声明一个数组
      let chains  = [dispatchRequest,undefined];//undefined占位
      //调用then方法指定回调
      let result = promise.then(chains[0],chains[1]);//执行第一个回调
      //返回promise结果
      return result;
    }

    //2.dispatchRequest函数
    function dispatchRequest(config) {  
      //调用适配器发送请求
      return xhrAdapter(config).then(response=>{
        return response
      },error=>{
        throw error
      })
    }

    //3.adapter适配器
    function xhrAdapter(config) {  
      console.log('xhrAdapter 函数执行');
      return new Promise((resolve,reject)=>{
        //发送ajax请求
        let xhr = new XMLHttpRequest();
        //初始化
        xhr.open(config.method,config.url);
        //发送
        xhr.send();
        //绑定事件
        xhr.onreadystatechange=function(){
          if(xhr.readyState==4){
            if(xhr.status==200){
              //成功状态
              resolve({
                //最终返回请求结果
                config:config,
                data:xhr.response,
                //....
              });
            }else{
              reject('请求失败')
            }
          }
        }
      })
    }

    //4.创建axios
    let axios  = Axios.prototype.request.bind(null)

    //5.使用
    axios({
      method:'GET',
      url:'http:/localhost:3000'
    }).then(response=>{
      console.log(response)

复制代码

axios拦截器

首先看看拦截器的执行:

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

复制代码
  • interceptors属性

还记得在axios创建过程中,Axios构造函数的初始化了这个属性,其中包含了(request、response)两个属性,

function Axios(instanceConfig) {
    this.interceptors = {
        request: new InterceptorManager(),
        response: new InterceptorManager()
    };
}

复制代码
1.InterceptorManager

以下就是InterceptorManager构造函数

function InterceptorManager() {
  //创建一个属性
  this.handlers = [];
}

InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  return this.handlers.length - 1;
};


复制代码

经过use方法的使用,上述的handlers就会有值(是个对象),就是我们在使用use方法传入的回调函数

2.执行顺序问题

之前多个拦截器的执行顺序问题还没有解决,现在放到这里来解决

//第一步
axios.interceptors.request.use(function one(res) {  
  console.log("请求拦截器成功1")
},function one(err) {  
  console.log("请求拦截器失败1")
})
//第二步
axios.interceptors.request.use(function two(res) {  
  console.log("请求拦截器成功2")
},function two(err) {  
  console.log("请求拦截器失败2")
})
//第三步
axios.interceptors.response.use(function one(res) {  
  console.log("响应拦截器成功1")
},function one(err) {  
  console.log("响应拦截器失败1")
})
//第四步
axios.interceptors.response.use(function two(res) {  
  console.log("响应拦截器成功2")
},function two(err) {  
  console.log("响应拦截器失败2")
})

axios({
  method:'GET',
  url:'http://localhost:3000'
}).then(res=>{
  console.log(res)
})

复制代码

我们来看执行顺序问题:

  1. 第一步执行完成之后,request对象上有属性handlers:一个数组对象包含两个回调函数one

image.png

  1. 第二、三、四步执行完成之后,request和response身上都有了对应的handlers【{one},{two}】

image.png

  1. axios开始发送请求,进入请求过程中request函数中,遍历请求拦截器
//4.创建拦截器中间件,undefined做补位
    var chain = [dispatchRequest, undefined];
//5. 创建一个成功的promise  
    var promise = Promise.resolve(config);
//6.遍历请求拦截器和响应拦截器
    this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {

        chain.unshift(interceptor.fulfilled, interceptor.rejected);
    });

    

复制代码

经过unshift操作之后,handlers对象数组中的每个对象包含的两个回调函数(成功/失败)都被压入chain中,chain数组变成如下这样:

image.png

  1. 通过push方法,来添加响应拦截器中的方法,最后chain数组的结果
//遍历响应拦截器
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
  
        chain.push(interceptor.fulfilled, interceptor.rejected);
    });

复制代码

image.png

  1. 接下来就使用shift方法对这些回调函数取出并且执行

shift是会改变原数组的,因此依次取出来的就是成功和失败的回调函数,最终遍历完整个chain数组,在Axios.rototype.request方法中返回一个promise结果,剩下的就交给axios().then来对结果做处理

  while (chain.length) {
        //依次取出 chain 的回调函数, 并执行
        promise = promise.then(chain.shift(), chain.shift());
    }
       return promise;


复制代码
3. 模拟实现
//1.Axios.prototype.request方法
function Axios(config) {
  //初始化
  this.defaults = config
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager(),
  }
}

Axios.prototype.request = function (config) {
  //创建一个promise对象
  let promise = Promise.resolve(config);
  //创建chains数组
  const chains = [dispatchRequest,undefined];
  //处理拦截器
  //请求拦截器放在chains前面
  this.interceptors.request.handlers.forEach(item=>{
    chains.unshift(item.fulfilled,item.rejected);
  })
  //响应拦截器放在chains后面
  this.interceptors.response.handlers.forEach(item=>{
    chains.push(item.fulfilled,item.rejected);
  })
  //遍历
  while(chains.length>0){
    promise = promise.then(chains.shift(),chains.shift())
  }
  return promise
}

//2.构建发送请求的函数dispatchRequest
function dispatchRequest() {  
  return new Promise((resolve,reject)=>{
    resolve({
      status:200,
    })
  })
}

//.3.构建axios
  //创建实例
  let context = new Axios({})
   //创建axios函数
   let axios = Axios.prototype.request.bind(context);
  //将context属性添加到axios上
  Object.keys(context).forEach(key=>{
    axios[key] =  context[key]
  })
  
//4.拦截器管理器构造函数
  function InterceptorManager() {
    this.handlers = []
  }
  InterceptorManager.prototype.use = function (fulfilled, rejected) {
    this.handlers.push({
      fulfilled,
      rejected,
    })
  }


//.5.设置请求/响应拦截器
  axios.interceptors.request.use(function one(res) {  
    console.log("请求拦截器成功1")
  },function one(err) {  
    console.log("请求拦截器失败1")
  })

  axios.interceptors.request.use(function two(res) {  
    console.log("请求拦截器成功2")
  },function two(err) {  
    console.log("请求拦截器失败2")
  })

  axios.interceptors.response.use(function one(res) {  
    console.log("响应拦截器成功1")
  },function one(err) {  
    console.log("响应拦截器失败1")
  })

  axios.interceptors.response.use(function two(res) {  
    console.log("响应拦截器成功2")
  },function two(err) {  
    console.log("响应拦截器失败2")
  })
  //6.使用
  axios({method:'GET'})

复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享