axios学习笔记

297 阅读8分钟

基础知识

1.请求体参数格式,在请求头中设置。

 Content-Type: application/x-www-form-urlencoded;charset=utf-8
    //用于键值对参数,参数的键值用=连接, 参数之间用&连接 例如: name=%E5%B0%8F%E6%98%8E&age=12
 Content-Type: application/json;charset=utf-8
    //用于json字符串参数 例如: {"name": "%E5%B0%8F%E6%98%8E", "age": 12}
 Content-Type: multipart/form-data
    //用于文件上传请求

2.http报文

请求报文 重点是格式与参数

行      POST  /s?ie=utf-8  HTTP/1.1 
头      Host: atguigu.com
        Cookie: name=guigu
        Content-type: application/x-www-form-urlencoded
        User-Agent: chrome 83
空行
体      username=admin&password=admin          //get请求中请求体可以为空

响应报文

行      HTTP/1.1  200  OK
头      Content-Type: text/html;charset=utf-8
        Content-length: 2048
        Content-encoding: gzip
空行    
体      <html>
            <head> </head>
            <body><h1>尚硅谷</h1></body>
        </html>

axios定义和基础知识

什么是axios

​ Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。

axios的特点

  • 基本promise的异步ajax请求库
  • 浏览器端/node端都可以使用
  • 支持请求/响应拦截器
  • 支持请求取消
  • 转换请求数据和响应数据
  • 批量发送多个请求
  • 自动转换 JSON 数据
  • 客户端支持防御 XSRF

axios请求方式

  • axios(config): 通用/最本质的发任意类型请求的方式
  • axios(url[, config]): 可以只指定url发get请求
  • axios.request(config): 等同于axios(config)
  • axios.get(url[, config]): 发get请求
  • axios.delete(url[, config]): 发delete请求
  • axios.post(url[, data, config]): 发post请求
  • axios.put(url[, data, config]): 发put请求
  • axios.all(promises): 用于批量执行多个异步请求

**注意:**一定要注意上方data和config的位置特别是get请求和post请求在设置请求头是有所不同。如下:

//GET 请求 两个参数:地址 , 配置参数
axios.get('/axios-server', {
  params: {id: 100},  //url 参数
  headers: {name: 'atguigu',age: 20}   //请求头信息
}).then(value => {
    console.log(value);
});
//POST 请求 三个参数:地址 , data(请求体参数) ,  配置参数
axios.post('/axios-server', {username: 'admin'}, {
    params: {vip: 9},  //url 
    headers: { height: 180} //请求头参数
});
//POST请求 form表单
axios({
      url: '/robot/doSaveRobot',
      method: 'post',
      headers: {
           'Content-type': 'application/x-www-form-urlencoded'
      },
      //请求数据
     data: this.robotindeft,
     transformRequest: [function (data) {
        let ret = ''
        for (let it in data) {
             ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'
        }
         return ret
      }],
 }).then(value => {console.log(value)})
 //或者
              const formdataAll = new FormData();
                for (let it in this.robotindeft) {
                    formdataAll.append(it, this.robotindeft[it])
                }
                axios.post('/robot/doSaveRobot',formdataAll).then(value => {
                    console.log(value)
                    if(value.data.resultcode==1){
                        this.$message(value.data.msg);
                    }else{
                        this.$message(value.data.msg);
                        this.addClose()
                    }
                });

配置

axios.defaults.xxx: 请求的默认全局配置
axios.interceptors.request.use(): 添加请求拦截器
axios.interceptors.response.use(): 添加响应拦截器

使用

直接使用(vue)

进入项目,npm安装npm install axios --save

在main.js下引用axios

import axios from 'axios';      
Vue.prototype.$axios=axios;  

使用

 this.$axios.post('/api/students',{  
     params: {  name:"admin", pass:123123},     //参数 
}).then(res => { console.log(res)      //请求成功后的处理数
}).catch(err => { console.log(err)     //请求失败后的处理数 
})    

二次封装

创建http.js文件(根据需求使用)

import axios from 'axios'
import { Message, MessageBox } from 'element-ui'
import store from '../store'

// 创建axios实例
const service = axios.create({
    //请求的默认全局配置
    baseURL: baseURL,
    timeout: 60000, //响应时长可根据业务需求自行设置
    responseType: "json",//请求数据类型包括  'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
    withCredentials: false,//表示跨域请求时是否需要使用凭证,是true的时候,开启withCredentials后,服务器才能拿到你的cookie,当然后端服务器也要设置允许你获取你开启了才有用
    headers: {
        'Content-type': 'application/json'
    }
})
  
// request拦截器
service.interceptors.request.use(
  config => {
    if (store.getters.token) {
      config.headers['X-Token'] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
    }
    return config
  },
  error => {
    // Do something with request error
    console.log(error) // for debug
    Promise.reject(error)
  }
)

// response 拦截器
service.interceptors.response.use(
  response => {
    /**
     * code为非20000是抛错 可结合自己业务进行修改
     */
    const res = response.data
    const codeReg = /^20\d+/
    if (!codeReg.test(response.status)) {
      Message({
        message: res.message,
        type: 'error',
        duration: 5 * 1000
      })

      // 50008:非法的token; 50012:其他客户端登录了;  50014:Token 过期了;
      if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
        MessageBox.confirm(
          '你已被登出,可以取消继续留在该页面,或者重新登录',
          '确定登出',
          {
            confirmButtonText: '重新登录',
            cancelButtonText: '取消',
            type: 'warning'
          }
        ).then(() => {
          store.dispatch('FedLogOut').then(() => {
            location.reload() // 为了重新实例化vue-router对象 避免bug
          })
        })
      }
      return Promise.reject('error')
    } else {
      return response.data
    }
  },
  error => {
    console.log('err' + error) // for debug
    Message({
      message: error.message,
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)

export default service

源码分析

axios 与 Axios 的关系?

在源码中有axios 与 Axios ,我们平时调用的是axios,而 Axios是一个构造函数。他们的关系是:

  1. 从语法上来说: axios 不是 Axios 的实例 ;
  2. 从功能上来说: axios 是 Axios 的实例
  3. axios 是 Axios.prototype.request 函数 bind()返回的函数
  4. axios 作为对象有 Axios 原型对象上的所有方法, 有 Axios 对象上所有属性

解释:在axios中有下面一段代码;axios是函数createInstance的执行结果,函数createInstance返回的是instance,instance也是一个函数,执行axios函数就是执行instance函数,而instance函数是Axios.prototype.request.bind(context)得来的,其实执行axio函数就相当于执行 Axios.prototype.request 函数,而且改变了this的指向。有 utils.extend(instance, Axios.prototype, context);这样一行代码,他的解释在代码的上方 .utils.extend(instance, context)context拷贝给instance,而通过var context = new Axios(defaultConfig);知道context是Axios的实例。所有就是将Axios实例对象上的属性拷贝到instance上: defaults和interceptors属性。此时axios有了Axios的属性和原型方法。所以说从功能上来说: axios 是 Axios 的实例,能通过axios点调用get方法;但是axios还是个函数,所有还能作为函数数调用。

function createInstance(defaultConfig) {
  /* 
  创建Axios的实例
      原型对象上有一些用来发请求的方法: get()/post()/put()/delete()/request()
      自身上有2个重要属性: defaults/interceptors
  */  
  var context = new Axios(defaultConfig);

  // axios和axios.create()对应的就是request函数
  // Axios.prototype.request.bind(context)
  var instance = bind(Axios.prototype.request, context); // axios

  // 将Axios原型对象上的方法拷贝到instance上: request()/get()/post()/put()/delete()
  utils.extend(instance, Axios.prototype, context); 

  // 将Axios实例对象上的属性拷贝到instance上: defaults和interceptors属性
  utils.extend(instance, context);

  return instance;
}

// Create the default instance to be exported
var axios = createInstance(defaults);

下面我们来看看Axios上的request()/get()/post()/put()/delete()方法怎么来的在Axios上有这么一段代码,意思是通过forEach将这几个方法添加到Axios的原型上,而他们最终调用的还是Axios的原型上的request方法。

// Provide aliases for supported request methods
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url
    }));
  };
});
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, data, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url,
      data: data
    }));
  };
});

运行过程

前面说的都是前期准备工作,现在开始看发送请求的过程,执行过程是三个函数request(config) ==> dispatchRequest(config) ==> xhrAdapter(config)

1.request(config):

将请求拦截器 / dispatchRequest() / 响应拦截器 通过 promise 链串连起来,返回 promise

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);
  // 添加method配置, 默认为get
  config.method = config.method ? config.method.toLowerCase() : 'get';

  /*
  创建用于保存请求/响应拦截函数的数组
  数组的中间放发送请求的函数
  数组的左边放请求拦截器函数(成功/失败)
  数组的右边放响应拦截器函数
  */
  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);
  });
  

  // 通过promise的then()串连起所有的请求拦截器/请求方法/响应拦截器
  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }

  // 返回用来指定我们的onResolved和onRejected的promise
  return promise;
};

**解读:**首先定义了一个promise和一个chain数组,在数组中保存着dispatchRequest(执行请求函数), undefined,通过两个forEach循环将请求拦截和响应拦截的成功失败函数保存到chain数组中。最后通过while循环两个两个的取出函数调用,最后返回promise在调用请求是书写的.then执行对应的onResolved和onRejected函数。

//axios内部会将定义的所有请求拦截的回调函数保存在requestInterceptors数组中
requestInterceptors: [{fulfilled1(){}, rejected1(){}}, {fulfilled2(){}, rejected2(){}}]
//axios内部会将定义的所有响应拦截的回调函数保存在responseInterceptors数组中
responseInterceptors: [{fulfilled11(){}, rejected11(){}}, {fulfilled22(){}, rejected22(){}}]
//通过上方源码可以看出forEach循环requestInterceptors数组,取出一项的两个函数(成功和失败)unshift进chai数组中,所有后定义的请求拦截回调会数组最前方先执行。responseInterceptors数组是push进chai数组的最后,所有响应回调顺序执行。
chain: [
      fulfilled2, rejected2, fulfilled1, rejected1, 
      dispatchReqeust, undefined, 
      fulfilled11, rejected11, fulfilled22, rejected22
    
]
//下方就是源码中while循环,promise链回调
promise链回调: config 
           => (fulfilled2, rejected2) => (fulfilled1, rejected1)   // 请求拦截器处理
           => (dispatchReqeust, undefined) // 发请求
           => (fulfilled11, rejected11) => (fulfilled22, rejected22) // 响应拦截器处理
           => (onResolved, onRejected) // axios发请求回调处理
2.dispatchRequest(config):

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

**注意:**请求数据转换中需要知道,在源码的defaults.js文件中有下面一段代码;axios会根据data的类型指定请求头中请求体参数格式类型

 // 请求转换器
  transformRequest: [function transformRequest(data, headers) {
    // 指定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();
    }
    // 如果data是对象, 指定请求体参数格式为json, 并将参数数据对象转换为json
    if (utils.isObject(data)) {
      setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
      return JSON.stringify(data);
    }
    return data;
  }],

**注意:**响应数据转换中需要知道,也是在defaults.js文件中,对响应的字符串类型进行转换为JSON类型,非字符串类型不做处理。

// 响应数据转换器: 解析字符串类型的data数据
  transformResponse: [function transformResponse(data) {
    /*eslint no-param-reassign:0*/
    if (typeof data === 'string') {
      try {
        data = JSON.parse(data);
      } catch (e) { /* Ignore */ }
    }
    return data;
  }],
3.xhrAdapter(config):

创建 XHR 对象, 根据 config 进行相应设置, 发送特定请求, 并接收响应数据,返回 promise