与服务端通信——axios

在实际项目中,页面所需要的数据通常是从服务端获取的,这必然牵涉与服务端的通信,Vue官方推荐使用axios完成Ajax请求。axios是一个基于Promise的HTTP库,可以用在浏览器和Node.js中。

安装

可以使用CDN方式安装:

  1. <script src="https://unpkg.com/axios/dist/axios.min.js"></script>

如果采用模块化开发,则使用npm安装方式:

  1. npm install axios --save

在vue的脚手架项目中使用,可以将axios结合vue-axios插件一起使用,该插件只是将axios集成到Vue.js的轻度封装,本身不能独立使用。可以使用以下命令安装:

  1. npm install axios vue-axios

安装vue-axios插件后,使用形式如下:

  1. import {createApp} from 'vue'
  2. import axios from 'axios'
  3. import VueAxios from 'vue-axios'
  4. const app = createApp(App);
  5. app.use(VueAxios,axios) //安装插件
  6. app.mount('#app')

之后在组件内就可以通过this.axios调用axios的方法发送请求。

基本用法

HTTP最基本的请求就是get和post。使用axios发送get请求调用形式如下:

  1. axios.get('/book?id=1')
  2. .then(function(response) {
  3. console.log(response);
  4. })
  5. .catch(function(error) {
  6. console.log(error);
  7. })

get方法接收一个URL作为参数,如果有要发送的数据,则以查询字符串的形式附加在URL后面。当服务端发回成功响应(状态码是2XX)时调用then()方法中的回调,可以在该回调函数中对服务端的响应进行处理;如果出现错误则会调用catch中的回调,可以在该回调函数中对错误信息进行处理,并向用户提示错误。

如果不喜欢URL后附加查询参数的写法,可以为get方法传递一个配置对象作为参数,在配置对象中使用params字段指定要发送的数据:

  1. axios.get('/book',{
  2. params:{
  3. id:1
  4. }
  5. })
  6. .then(function(response) {
  7. console.log(response);
  8. })
  9. .catch(function(error) {
  10. console.log(error);
  11. })

可以使用ES2017的async/await执行异步请求:

  1. async function getBook(){
  2. try {
  3. const response = await axios.get('/book?id=1');
  4. console.log(response);
  5. } catch(error){
  6. console.error(error);
  7. }
  8. }

post请求是在请求体中发送数据,因此,axios的post方法比get方法多一个参数,该参数是一个对象,对象的属性就是要发送的数据:

  1. axios.post('/login',{
  2. username:'lisi',
  3. password:'1234'
  4. })
  5. .then(function(response){
  6. console.log(response);
  7. })
  8. .catch(function(error) {
  9. console.log(error);
  10. });

get()和post()方法的原型如下:

  1. get(url[,config])
  2. post(url[,data[,config]])

接收到服务端的响应信息后,需要对响应信息进行处理。例如,设置用于组件渲染或更新所需要的数据。回调函数中的response是一个对象,该对象常用的属性是data和status,前者用于获取服务端发回的响应数据,后者是服务端发送的HTTP状态码。

response对象的完整属性:

  1. {
  2. //data是服务器发回的响应数据
  3. data:{},
  4. //服务器响应的状态码
  5. status:200,
  6. //状态描述
  7. statusText:'OK',
  8. //headers是服务器响应的消息报头,所有报头的名字都是小写的
  9. //可以通过response.headers['content-type']
  10. headers:{},
  11. //config是为请求提供的配置信息
  12. config:{},
  13. //request是生成此响应的请求
  14. request:{}
  15. }

成功响应后,获取数据的一般处理形式如下:

  1. axios.get('/book?id=1')
  2. .then(function(response){
  3. if(response.status === 200) {
  4. this.book = response.data;
  5. }
  6. })
  7. .catch(function(error) {
  8. console.log(error);
  9. });

如果出现错误则会调用catch方法中的回调,并向该回调函数传递一个错误对象。错误对象的一般形式如下:

  1. axios.get('/book?id=1')
  2. .catch(function(error) {
  3. if (error.response) {
  4. //请求已发送并接收到服务端响应,但响应的状态码不是2XX
  5. console.log(error.response.data);
  6. console.log(error.response.status);
  7. console.log(error.response.headers);
  8. } else if (error.request) {
  9. //请求已发送,但未接收到响应
  10. console.log(error.request);
  11. } else {
  12. //在设置请求时出现问题而引发错误
  13. console.log('Error',error.message);
  14. }
  15. console.log(error.config);
  16. });

axios API

可以通过向axios传递相关配置来创建请求。axios原型如下:

  1. axios(config)
  2. axios(url[,config])

get请求和post请求的调用形式如下:

  1. //发送get请求(默认的方法)
  2. axios('/book?id=1');
  3. //get请求,获取远端的图片
  4. axios({
  5. method:'get',
  6. url:'/images/logo.png',
  7. responseType:'stream'
  8. })
  9. .then(function(response) {
  10. response.data.pipe(fs.createWriteStream('logo.png'))
  11. });
  12. //发送post请求
  13. axios({
  14. method:'post',
  15. url:'/login',
  16. data:{
  17. username:'lisi',
  18. password:'1234'
  19. }
  20. });

为了方便使用,axios库为所有支持的请求方法提供了别名:

  • 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]])

在使用别名方法时,url、method、data这些属性都不必在配置对象中指定。

请求配置

axios库为请求提供了配置对象,在该对象中可以设置很多选项,常用的是url、method、headers和params:

  1. {
  2. url:'/book',
  3. method:'get',
  4. //baseURL将自动加在url前面,除非url是一个绝对URL
  5. //为axios实例设置一个baseURL就可以将相对URL传递给该实例的方法
  6. baseURL:'https://some-domain.com/api/',
  7. //transformRequest允许在将请求数据发送到服务器前对其修改
  8. //只能用于put、post、patch、delete这几个请求方法
  9. //数组中的最后一个函数必须返回一个字符串、Buffer的实例、ArrayBuffer、FormData或Stream
  10. //也可以修改headers对象
  11. transformRequest:[function(data,headers) {
  12. //对data进行任意转换处理
  13. return data;
  14. }],
  15. //transformResponse允许在将响应数据传递给then/catch之前对其修改
  16. transformResponse:[function(data) {
  17. //对data进行任意转换处理
  18. return data;
  19. }],
  20. //是要发送的自定义请求头
  21. headers:{'X-Requested-With':'XMLHttpRequest'},
  22. params:{
  23. ID:1
  24. },
  25. //是一个负责params序列化的可选函数
  26. paramsSerializer:function(params) {
  27. return Qs.stringify(params,{arrayFormat:'brackets'})
  28. }
  29. data:{
  30. firstName:'Fred'
  31. },
  32. //只发送值,不发送键
  33. data:'Country=Brasil&City=Belo',
  34. timeout:1000,//请求超过timeout则终止
  35. withCredentials:false, //默认值,表示跨域请求时是否需要凭证
  36. //adapter允许自定义处理请求,以使测试更加容易
  37. //返回一个promise并提供一个有效的响应
  38. adapter:function(config) {
  39. ...
  40. },
  41. //auth表示应该使用HTTP基础验证,并提供凭据
  42. //这将设置一个Authorization报头,覆盖使用headers设置的现有的Authorization自定义报头
  43. auth:{
  44. username:'zzd',
  45. password:'1234'
  46. }
  47. responseType:'json', //默认的,响应的数据类型
  48. responseEncoding:'utf8' //解码响应数据的编码
  49. ...
  50. }

并发请求

有时需要同时向服务端发起多个请求,这可以用Promise.all实现,例如:

  1. function getUserAccount() {
  2. return axios.get('/user/12345');
  3. }
  4. function getUserPermissions() {
  5. return axios.get('/user/12345/permissions');
  6. }
  7. Promise.all([getUserAccount(),getUserPermissions()])
  8. .than(function(results) {
  9. //两个请求现在都执行完成
  10. const acct = result[0]; //响应结果1
  11. const perm = result[1]; //响应结果2
  12. })

创建实例

可以使用自定义配置调用axios.create([config])方法创建一个axios实例,之后使用该实例向服务端发起请求,就不用每次请求时重复设置配置选项了:

  1. const instance = axios.create({
  2. baseURL:'https://some-domain.com/api/',
  3. timeout:1000,
  4. headers:{
  5. 'X-Custom-Header':'foobar'
  6. }
  7. })

配置默认值

对于每次请求相同的配置选项,可以通过为配置选项设置默认值来简化代码的编写。项目中用的全局axios默认值可以在项目的入口文件main.js中按照以下形式设置:

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

也可以在自定义实例中设置配置默认值,这些配置选项只有在使用该实例发起请求时才生效:

  1. //创建实例时设置配置默认值
  2. const instance = axios.create({
  3. baseURL:'http://api.example.com'
  4. });
  5. //实例创建后更改默认值
  6. instance.defaults.headers.common['Authorization'] = AUTH_TOKEN;

配置将按优先顺序进行合并。顺序是在lib/defaults.js中找到的库的默认值,然后是实例的defaults属性,最后是请求的config参数。后者将优先于前者。

  1. var instance = axios.create();
  2. instance.default.timeout = 2500;
  3. instance.get('/longRequest'.{
  4. timeout:5000
  5. })

拦截器

有时需要统一处理HTTP的请求和响应,如登录验证,这时就可以使用axios的拦截器,分为请求拦截器和响应拦截器,他们会在请求或响应被then()或catch()方法处理前拦截它们。axios的拦截器的使用形式如下:

  1. //添加请求拦截器
  2. axios.interceptors.request.use(function(config) {
  3. //在请求发送前做些什么
  4. return config;
  5. }, function (error) {
  6. //对请求错误做些什么
  7. return Promise.reject(error);
  8. });
  9. //添加响应拦截器
  10. axios.interceptors.response.use(function(response) {
  11. //在响应数据做些什么
  12. return response;
  13. }, function (error) {
  14. //对请求错误做些什么
  15. return Promise.reject(error);
  16. });

前面章节使用全局守卫实现了一个用户登录验证的例子,不过这种方式只是简单的前端路由控制,用户一旦登录成功,前端就保存了用户登录的状态,允许用户访问受保护的资源。如果在这期间,该用户在服务端失效了,比如用户长时间未操作、服务端强制下线、或者管理员将该用户拉入黑名单,那么前端就应该及时更新用户的状态,对用户的后续访问做出控制。在这种情况下,就应该使用axios的拦截器结合HTTP状态码进行用户是否已登录的判断:

  1. //请求拦截器
  2. axios.interceptors.request.use(
  3. config => {
  4. if (token) {
  5. //判断是否存在token,如果存在,则每个HTTP header都加上token
  6. config.headers.Authorization = `token ${store.state.token}`;
  7. }
  8. return config;
  9. },
  10. err => {
  11. return Promise.reject(err);
  12. });
  13. //响应拦截器
  14. axios.interceptors.response.use(
  15. response => {
  16. return response;
  17. },
  18. error => {
  19. if (error.response) {
  20. switch (error.response.status) {
  21. case 401:
  22. //如果返回401,则清除token信息并跳转到登录页面
  23. router.replace({
  24. path:'login',
  25. query:{redirect:router.currentRoute.fullPath}
  26. })
  27. }
  28. }
  29. return Promise.reject(error.response.data)
  30. });

如果之后想移除拦截器,则可以:

  1. const myInterceptor = axios.interceptors.request.use(function(){...});
  2. axios.interceptors.request.reject(myInterceptor);

也可以为自定义的axios实例添加拦截器:

  1. const instance = axios.create();
  2. instance.interceptors.request.use(function(){});