简述

欢迎来到新的一篇,axios篇,axios是一个基于Promise的网络请求库,它并不是vue中的插件,所以在使用时
并不需要Vue.use(axios),讲axios之前说一下promise是什么,下面是本篇的主要内容:

Promise的基本使用

在JavaScript的世界中,所有代码都是单线程执行的。由于这个“缺陷”,导致JavaScript的所有网络操作,
浏览器事件,都必须是异步执行。因为网络请求这些操作需要时间,如果同步执行,用户不得不等待网络请求完毕才能得到响应。
像ajax,定时器等等,就是异步操作,这些异步操作会在将来调用我们提供的回调函数,但是Ajax这种异步操作
写起来太难看了,如果网络请求多了起来,后一个网络请求需要前一个网络请求的结果,那么代码就会形成回调地狱
所以Promise就出现了,它是ES6提供的对象,能更优雅的写异步请求,避免掉回调地狱这种情况。
下面是Promise的具体写法:

new Promise((resolve,reject)=>{
    const flag=true
    setTimeout(()=>{
        console.log("执行定时任务");
        if (flag){
            resolve("异步操作执行成功");
        }else {
            reject("异步操作执行失败")
        }
    },1000);
}).then(res=>{		//箭头函数,只有一个参数时,()可以省略
    console.log(res);
}).catch(error=>{
    console.log(error);
})

/*
	promise对象有两个参数,resolve,reject,参数名随意啊,我想说的是,这两个参数都是函数
	并且是then,catch方法中的回调函数,我们提供的,当执行resolve方法时,就会执行then中的方法
	当执行reject方法时,就会执行catch中的方法。
*/

上面的案例还是挺简单的,下面看看复杂一点的,在基本使用上做了一些简化操作

new Promise(resolve=>{	//如果只用resolve方法,则reject参数可以省略
    setTimeout(()=>{
        resolve("Hello");
    },1000)
}).then(res=>{	//res就是Hello
    console.log("第零次的处理");
    return new Promise(resolve=>{	//返回一个promise对象后,继续使用then处理
        setTimeout(()=>{
            resolve(res+" Wo")
        },1000)
    })
}).then(res=>{	//res就是HelloWo
    console.log('第一次的处理');
    return Promise.resolve(res+" World")  //对上面进行简化,直接调用resolve方法
}).then(res=>{	//res接收到HelloWoWorld
    console.log('第二次的处理');
    return res+" giao"	//直接返回字符串,对上面再一次简化,相当于 return Promise.resolve(res+" Giao")
}).then(res=>{
    console.log(res);	//输出HelloWoWorldGiao
}).catch(err=>{		//上面代码没有执行reject,这里不执行
    console.log(err);
})

/*
	提示下,只要一个函数返回promise对象,那么我在调用这个函数后,就可以紧跟着调用then方法或catch方法
	并不一定说then,catch方法和promise对象放在一起,比如在axios中就有相应的用法,vuex篇我们也使用过。
*/

发出网络请求方式有哪些

  • 传统的ajax是基于XMLHttpRequest(XHR),但是在开发中并不使用,太繁琐

  • Jquery中的ajax代替传统的ajax,这是以前的情况,但是vue项目中,vue本身1W+行,Jquery也是1w+行
    所有完全没必要为了用网络请求就引用这个重量级库,目前三大框架都有自己的网络请求模块,所以都不使用这种方式

  • vue-resource,是vue1.0时vue官方推出的网络请求框架,但在vue2.0时被淘汰了,官方选择了axios

  • 还有一种,应该说是一种请求方式,而不是网络请求框架,jsonp
    使用jsonp最主要的原因往往是为了解决跨域访问的问题,不过只能解决get方式的跨域访问
    ajax因为浏览器的同源策略,不能进行跨域请求,而jsonp是通过<script>标签的src来帮助我们请求数据
    jsonp的方式是请求时url后跟上一个callback参数,该参数值是一个回调函数的函数名,我们指定服务器返回的JS函数名称
    函数的参数就是后端响应的数据,前端通过script标签的src发送请求,请求完成后,就可以使用这个数据。
    下面是个伪代码:

    //前端
    <script src="http://www.keyi.world/getUser?callback="getUserInfo""></script>
    //回调函数
    getUserInfo(res){	//该函数被回调,res接收到服务器传递的数据,即"真实数据"
    	//对res进行处理
    }
    
    //后端,后端返回的数据是一个函数,并在函数的参数中返回真实数据,相当于调用
    return "getUserInfo("真实数据")"
    
    //这也就解释了为什么jsonp只能处理get请求
  • axios,内部基于promise对ajax进行了封装,不仅可以在浏览器中发出ajax,还可以在node.js中发送http请求
    你还可以拦截请求和响应,在发出请求前后做一些处理。

axios的基本使用

  • 安装axios,运行时依赖:npm install axios -S

  • 配置跨域
    网络请求不可避免的会产生跨域问题,在项目根目录下创建vue.config.js文件,配置跨域如下:

      module.exports = {
          devServer: {
              proxy: {
                  '/home': { //请求的代称,写在Axios里的BaseUrl
                      target: 'http://api.qingyunke.com',
                      ws: true,
                      changeOrigin: true, 
                      pathRewrite: {
                          '^/home': '' 
                          // '^/home': '/home'
                      }
                  }
              }
          }
      }
      
    /*	
      	"/home",就是配置一个url前缀,当在请求url中的端口号后加上这个/home,
      		就表示该请求是跨域请求就会将请求发给target指定的url中
      		如果发送请求时不加上这个url前缀,则会去找前端项目的public目录下的资源,即该请求不被本地服务器代理
      	ws属性表示,是否支持webSocket,默认为true
        changeOrigin属性表示,是否暴露请求头的host字段,如果为false,则请求后端时,后端获取的host值是真实的
      		如果为true,则表示隐藏自己的host字段,后端接收的host值是自己的服务器host值,默认值为true
      	pathRewrite属性表示,当我们通过代理访问请求后,因为设置了url前缀,我们的访问路径变成了:/home/api.php,
        	但是我们实际的api.php页面使用/api.php请求才能被访问到,所以通过pathRewrite路径重写,将请求前缀去除
      */
  • 在main.js中使用axios

    import axios from 'axios'
    //将url前缀设置为默认路径,这里的配置会将所有请求都会加上url前缀,即所有请求都被代理
    axios.defaults.baseURL ='/home';    
    axios({
        url: "/api.php",  //如果没有指定method属性,则默认使用get请求,这里直接跟baseURL后面的地址即可
        params: {	//param就是url中?后跟的参数
            key: 'free',
            appid: 0,
            msg: '你好吗'
        }
    }).then(res=>{      //axios内部返回了promise,所以这里调用then方法,能拿到返回结果
        console.log(res);
    })
    
    /*
    	axios请求方式还有很多,下面是具体方式:
    		1)axios(config),默认情况下,如果只有url,则请求方式是get,设置method可以设置请求方式
            2)axios.request(config)
            3)axios.get(url[,config])	[]表示可选
            4)axios.delete(url[,config])
            5)axios.head(url[,config])
            4)axios.post(url[,data[,config]])
            4)axios.put(url[,data[,config]])
            4)axios.patch(url[,data[,config]])
    */

axios发出并发请求

发送两个请求,两个请求都拿到结果再去做其他事情,案例如下,估计内部也是使用了promise的all方法

//baseURL,跨域等配置同上
axios.all([
    axios({
        url: "/api.php",
        params: {
            key: 'free',
            appid: 0,
            msg: '你叫什么名字'
        }
    }),
    axios({
        url: "/api.php",
        params: {
            key: 'free',
            appid: 0,
            msg: '杭州天气怎么样'
        }
    })
]).then(res=>{
    console.log(res);	//返回结果是数组,数组元素就是两个请求的结果
})

axios全局配置

全局配置指的是,抽离请求中的一些固定部分,例如,请求前缀(协议+域名+端口号),请求头设置一些固定值等等

原始配置:
    axios({
        url: "http://api.qingyunke.com/api.php",
        timeout: 5000,  //超时时间
        params: {       //请求参数
            key: 'free',
            appid: 0,
            msg: '你好吗'
        }
    }).then(res=>{
        console.log(res);
    })

配置抽离:
	//这里配置的是全局,你也可以在axios内部配置baseURL,跟上面例子中/home是一样的
    axios.defaults.baseURL ='http://api.qingyunke.com'  
    axios.defaults.timeout=5000
    axios({
        //baseURL: ''
        url: "/api.php",
        params: {       //请求参数
            key: 'free',
            appid: 0,
            msg: '你好吗'
        }
    }).then(res=>{
        console.log(res);
    })

/*
那么,axios中传入的config对象中除了以上url,param,baseURL,timeout属性外,还可以有哪些属性?
	请求地址:url: '/user'
    请求类型:method: 'get'
    请求根路径:baseURL: 'http://www.keyi.world'
    请求前的数据处理:transformRequest: [function(data){}]   //可以传入多个函数
    请求后的数据处理:transformResponse: [function(data){}]
    自定义的请求头:headers:{'x-Request-With':'XMLHttpRequest'}
    URL查询对象:params:{id:12}      //就是url携带的参数
    查询对象序列化函数:paramsSerializer: function(params){}
    request body:data:{key:'aa'}
    超时设置:timeout:1000
    跨域是否带token:withCredentials:false
    自定义请求处理:adapter:function(resolve,reject,config){}
    身份验证信息:auth:{uname:'',pwd:'12'}
    响应式的数据格式:responseType:'json'   //有json/raw/document/arraybuffer/text/stream  
*/

axios的实例

我们从axios模块中导入对象后,使用的实例是默认的实例,当给该实例设置一些默认配置时,这些配置就被固定下来了
但是后续开发中,某些配置可能会不太一样,比如某些请求需要使用特定的baseURL或者timeout或者content-type等
这个时候,我们就可以创建新的实例,并且传入属于该实例的配置信息
简而言之,不同的请求需要不同的配置,我们之前使用的都是默认的axios实例,所以现在我们需要创建新的axios实例
创建axios实例去发送请求,每个axios对应不同的配置,下面是一个简单的案例:

//这里的/home配置同上,配置了跨域。
let axiosInstance = axios.create({      //这里的axiosInstance是一个axios实例,保存配置的
    baseURL: '/home',
    timeout: 5000
});
axiosInstance({         //使用对应的配置实例来发送网络请求,做到请求配置和实际请求的分离
    url: "/api.php",
    params: {
        key: 'free',
        appid: 0,
        msg: '江西省九江市彭泽县'
    }
}).then(res=>{
    console.log(res);
})

根据代码规范对axios进行封装

如果我们的组件依赖某个第三方框架,我们最好在main.js入口函数中全局注册一下,例如组件使用axios,
最好不要每个组件都import导入axios模块,而是在main.js中,Vue.prototype.$axios=axios,来全局注册
这样我们其他组件可以通过this.$axios得到axios的对象,因为组件都是Vue构造函数的实例,
也就继承了Vue原型对象的变量和方法。

另外,需要说明的是上面这种做法其实组件还是没有和axios框架分离,因为组件内还是使用了this.$axios
所以一般开发中都会再对axios做一层封装,让我们的组件依赖我们自己的封装后的对象,
这样,当我们将axios换成其他框架时,我们只需要更改自己封装的内容而不需要修改组件!

按照代码规范,对axios进行封装:

//src下创建network目录,其中再创建request.js文件,该文件内容如下:
import axios from 'axios'
export function request(config) {
    //创建一个axios实例
    let axiosInstance = axios.create({    //这个对象感觉可以抽离,让使用者配置,但是也可以在本js文件中创建多个函数
        baseURL: '/home',
        timeout: 5000
    });
    //发送网络请求
    return axiosInstance(config)    //本身返回的就是promise,所以使用者调用request方法后可直接跟then函数
}
//之所以不用默认导出,因为可能你会有多个函数,每个函数有不同的axios实例去使用,如果默认导出,则只能导出一个

//使用者使用时:
import {request} from './network/request';
request({
  url: "/api.php",
  params: {
    key: 'free',
    appid: 0,
    msg: '我爱你'
  }
}).then(res=>{	//直接可以拿到网络请求的响应结果
  console.log(res);
}).catch(res=>{	//如果网络请求出错,也可以拿到失败结果
  console.log(res);
})

axios的拦截器

axios提供了拦截器,用于我们在每次发送请求之前或者得到响应后,进行相应的处理
下面是拦截器案例,也一并放到封装的axios中去了

import axios from 'axios'
export function request(config) {
    //1,创建一个axios实例
    let axiosInstance = axios.create({
        baseURL: '/home',
        timeout: 5000
    });
    //2,配置拦截器
    //请求拦截,有两个回调函数,请求成功函数,请求失败回调函数
    axiosInstance.interceptors.request.use(config=>{
        console.log("发送请求之前执行");
        /*
          请求拦截器使用场景:
              * 比如config中的一些信息不符合服务器的要求
              * 比如每次发送网络请求时,都希望在界面中显示一个请求图标
              * 某些网络请求需要token,必须携带一些特殊的信息,在这里可以添加到请求头中
         */
        return config;      //拦截器放行,不然响应就会失败(不放行代表请求失败),这个函数会在请求之前执行	
    },err=>{
        //这里一般只要不是网络原因,都很少来到这里,这里指的是发送请求发布出去,如果是错误url,还是可以发送的出去的   
        console.log(err);   //如果请求失败,则打印错误信息
    })
    //响应拦截,响应成功回调函数,响应失败回调函数
    axiosInstance.interceptors.response.use(res=>{
        return res;   //响应成功后不放行的话,使用者的then方法将拿不到响应的数据,所以需要将响应数据放行
    },error => {
        console.log(error); //如果响应失败,则打印错误信息,也可能是请求不放行导致的
    })
    //3,发送网络请求
    return axiosInstance(config)    //本身返回的就是promise
}