axios+promise整合http请求

前言

当以前后端分离的方式进行项目的开发时,我们可以简单地把前端开发看做页面展示的开发,把后端开发看做数据处理的开发。前端页面的展示,会根据需求去后端请求相应的数据。

后端是以URL的方式暴露接口来提供服务的,也就是说前端需要根据需求对应的URL组装http请求,去调用后端接口获取数据并将展示在页面上。前端项目的实际开发中,经常使用axios + promise来整合http请求。这篇博客就是对axios + promise整合http请求的相关知识点进行总结。

目录

  1. Ajax相关知识点
  2. Promise相关知识点
  3. Axios相关知识点

Ajax相关知识点

AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。 AJAX 不是新的编程语言,而是一种使用现有标准的新方法。在不重新加载整个页面的情况下, AJAX 与服务器交换数据并更新部分网页的内容。

—参考自W3school的ajax教学

ajax的相关代码如下:

// 创建XMLHttpRequest对象
var request;
if (window.XMLHttpRequest) {
  // IE7+和其他
  request = new XMLHttpRequest();
} else {
  // IE6或IE5
  request = new ActiveXObject("Microsoft.XMLHTTP");
}
// 当http请求的状态发生变化时,会调用onreadystatechange()方法
request.onreadystatechange = function(){
  // 当readyStatue = 4且status = 200时,表示http请求成功完成
  if (request.readyState === 4 && request.status === 200) {
    // 获取http请求返回的字符串类型的响应文本
    var response = request.responseText;
    // 通过json解析后,将响应文本的值填充到页面对应的元素中
    var jsonObj = JSON.parse(response);
    document.getElementById("date").innerHTML =  jsonObj.date;
    document.getElementById("time").innerHTML = jsonObj.time;
  }  
}
// 规定http请求的的类型、路径、是否异步
request.open('method_type','url',async);
// 如果是post请求提交表单数据,需要设置请求头
request.setRequestHeader("Content-type","application/x-www-form-urlencoded");
// 发送http请求
request.send();

一个最常见的适合使用ajax的场景就是:注册用户时的表单校验。用户注册demo对应的原生html代码如下:

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
	<head>
		<meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
	</head>
	<body>
		<div id="test" class="test">
			<form id="form" method="post" action="register_url">
				<p id="register-email">&nbsp;&nbsp;箱:<input type="text" name="email"/>
				</p>
				<p id="register-mobile">&nbsp;&nbsp;机:<input type="text" name="mobile"/>
				</p>
				<p id="register-username">
					用户名称:<input type="text" name="username"/>
				</p>
				<p id="register-password">&nbsp;&nbsp;码:<input type="password" name="password"/>
				</p>
				<p id="register-password-confirm">
					确认密码:<input type="password" name="passwordConfirm"/>
				</div>
				<p id="button-area">
					<button id="register-btn" type="submit">注册</button>
				</p>
			</form>
		</div>
	</body>
</html>

当点击“注册”按钮时,会发送一个POST类型、url为register_url的http请求。这个请求对应的后端接口,需要先对用户填写的信息进行校验。如果校验通过,后端会新增一条用户记录然后告知前端注册成功;如果校验失败,后端则会报错的详细信息返回给前端。

Web的运作原理:一次HTTP请求对应一个页面。也就是说,我们用一般的表单提交进行用户注册,当录入的用户信息校验未通过或遇到网络问题时,其实会跳转到其他的页面(如404页面等等)。但要使用注册功能只能在注册页面,这个时候注册页面就会重新加载一次(也就是我们可能会遇到的浏览器闪了一下)。重新加载页面,我们之前录入的信息就会消失。如果多次校验失败,我们就要多次填写重复信息,用户体验非常差。

保证发送的http请求出现异常时不会刷新网页,这个时候就要考虑使用ajax。这里就只需要改装表单的提交方式,改为给“注册”按钮添加一个点击事件,在这个事件中去用ajax调用后端的注册接口即可。

Promise相关知识点

Promise对象用于表示一个异步操作的最终状态(完成或失败),以及该异步操作的结果值。

—参考自Promise中文官网

Promise对象只有三种状态:pending(进行中)fulfilled(已完成)rejected(已拒绝)Promise对象创建后的初始状态就是pending,它的构造函数中有两个函数参数:resolvereject。构造函数内部调用resolve()会将当前Promise对象的状态从pending变为fulfilled;构造函数内部调用reject()会将当前Promise对象的状态从pending变为rejected

由于ES6实现了Promise,而现在的主流浏览器基本支持ES6的绝大部分规范,因此我们可以直接在浏览器的控制台中键入js代码进行测试。

在浏览器的控制台中键入如下代码:

function runAsync(count) {
	console.log('第' + count + '次调用runAsync()');
	let promise = new Promise(function(resolve, reject){
		// 模拟异步请求
		setTimeout(function(){
			let num = Math.random();
			if (num > 0.5) {
				console.log('第' + count + '次的结果大于0.5,调用resolve()将promise状态设置为fulfilled');
				resolve();
			} else {
				console.log('第' + count + '次的结果不大于0.5,调用reject()将promise状态设置为rejected');
				reject();
			}
		}, 1000);
	});
	return promise;
}
runAsync(0);
runAsync(1);
runAsync(2);

这段代码的意思很简单—声明了一个返回promise对象的函数runAsync()。该promise的构造函数中有一个延时定时器,任务是:1秒之后生成一个0-1之间的随机数,当随机数大于0.5时把promise的状态设置为fulfilled,否则设置为rejected

测试效果如下:

Promise测试效果图-1

Promise的中文意思就是:承诺。promise是一个代理对象,它代理的是其构造函数内部的一个事件,通过判断promise对象的状态,我们能够获取promise所代理的那个事件将来的执行结果。基于promise对象的状态也就是被代理事件的执行结果是成功(promise的状态为fulfilled)还是失败(promise的状态为rejected),来做一些基于结果的额外操作。获取事件最终结果主要使用它的then()方法和catch()方法。

Promise的then()catch()

当promise的状态是fulfilled时,会执行then()中的第一个函数参数;当promise的状态是rejected时,会执行then()中的第二个函数参数或catch()的第一个函数参数。

测试的代码如下:

function runAsync() {
  let promise = new Promise(function(resolve, reject){
    setTimeout(function(){
      let num = Math.random();
      if (num > 0.5) {
        resolve('<fulfilled>' + num);
      } else {
        reject('<rejected>' + num);
      }
    }, 1000)
  })
  return promise;
}
runAsync().then(res => {
  console.log('事件成功:' + res);
}, err => {
  console.log('事件失败:' + err);
})
runAsync().catch(err => {
  console.log('事件失败:' + err);
})

测试效果如下:

Promise测试效果图-2

catch()对事件失败的处理与then()中第二个函数参数是一样的。这里可以自行测试。

在1秒之后获取一个0-1的随机数,这个随机数与0.5比大小的结果是不可期的。但我们可以通过Promise的状态预先定义出:当这个事件未来的执行结果为成功时(resolve()将状态设置为fulfilled),控制台打印“事件成功”;当事件未来的执行结果为失败时(reject()将状态设置为rejected),控制台打印“事件失败”。这对于js编程是很有意义的,由于js是单线程执行的,碰到耗时长的网络请求时如果让线程等待到拿到最终的执行结果再进行其他的操作,那用户的体验是非常糟糕的。这时就可以考虑使用promise来封装网络请求。

then()catch()的返回值也是promise对象,因此可以通过链式操作来进行同步操作。

测试代码如下:

function runAsync() {
  let promise = new Promise(function(resolve, reject){
    setTimeout(function(){
      let num = Math.random();
      if (num > 0.5) {
        resolve('<fulfilled>' + num);
      } else {
        reject('<rejected>' + num);
      }
    }, 1000)
  })
  return promise;
}
runAsync().then(res => {
  console.log('事件成功:' + res);
  return '<1>';
}).then(res => {
  console.log('接收前一个promise中的返回值:' + res);
  return '<2>';
}).then(res => {
  console.log('接收前一个promise中的返回值:' + res);
  let a = undefined;
  console.log(a.index);
}).catch(err => {
  console.log('事件失败:' + err);
});

测试效果如下:

Promise测试效果图-3

从这段代码我们可以看到,then()的链式操作中第一个参数就是上一个函数的返回值;catch()不仅能够在promise对象处于rejected状态进行操作,也能够捕获链式操作前面出现的异常,保证js不卡在这里。

Promise的all()race()

上面我们通过链式操作能够进行同步执行,现在我们可以学习all()race()进行异步执行。all()中promise参数集合对应的所有事件同时执行,只有所有事件的结果都是成功才算成功(fulfilled),否则就是失败(rejected)。race()是所有事件进行竞赛,最先执行完成的事件结果就是其结果。

测试代码如下:

function runAsync1() {
  let promise = new Promise(function(resolve, reject){
    setTimeout(function(){
      let num = Math.random();
      if (num > 0.5) {
        resolve('runAsync1()-fulfilled: ' + num);
      } else {
        reject('runAsync1()-rejected: ' + num);
      }
    }, 3000)
  })
  return promise;
}
function runAsync2() {
  let promise = new Promise(function(resolve, reject){
    setTimeout(function(){
      let num = Math.random();
      if (num > 0.5) {
        resolve('runAsync2()-fulfilled: ' + num);
      } else {
        reject('runAsync2()-rejected: ' + num);
      }
    }, 1000)
  })
  return promise;
}
function runAsync3() {
  let promise = new Promise(function(resolve, reject){
    setTimeout(function(){
      let num = Math.random();
      if (num > 0.5) {
        resolve('runAsync3()-fulfilled: ' + num);
      } else {
        reject('runAsync3()-rejected: ' + num);
      }
    }, 2000)
  })
  return promise;
}

// all()的测试
Promise.all([runAsync1(), runAsync2(), runAsync3()])
.then(res => {
  console.log(res);
}).catch(err => {
  console.log(err);
})

// race()的测试
Promise.race([runAsync1(), runAsync2(), runAsync3()])
.then(res => {
  console.log(res);
}).catch(err => {
  console.log(err);
})

all()的测试效果图如下:

Promise测试效果图-4

race()的测试效果图如下:

Promise测试效果图-5

从效果图可以看出,all()只有当所有的promise参数的最终状态是fulfilled时,自身的状态才会是fulfilled,也就是去执行其后then()中第一个函数参数,并且会把所有的返回值按数组顺序返回,否则自身状态就是rejected,并且会返回最先返回rejected的那个promise参数对应事件的返回值。

race()就是所有的promise参数对应的事件进行竞速,以最快执行完成的事件对应的promise参数的最终状态为准。这里runAsync2()的延时最短(1s),因此是以它的最终状态为准。

Axios相关知识点

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

—参考自Axios中文官网

简单来说,axios就是对promise进行了一次封装。通过使用axios,我们能够在项目中很便捷轻松地发送HTTP请求。

假设有后端源码的伪代码如下:

// 配置项目端口8090
server.port: 8090

// 实体类Student
class Student {
  Long id;
  String name;
  Integer age;
  String phoneNum;
  //...更多属性
}

// Student对应接口层
@RequestMapping("/springboot_jpa")
public class StudentController{

  @GetMapping("/student/findAll")
    public List<Student> findAll(){
        return studentService.findAll();
    }

  @RequestMapping("/student/findByNameLike")
    public List<Student> findByNameLike(@RequestParam("surname") String surname){
        return studentService.findByNameLike( surname );
    }
}

成功启动后端项目并在地址栏中发送HTTP请求(http://localhost:8090/springboot_jpa/student/findAll),获取当前所有的学生信息效果图如下:

axios-1

利用vue-cli的脚手架工具快速搭建前端项目,并测试axios的相关命令如下:

# 利用vue脚手架搭建前端项目axios-test
vue init webpack axios-test
cd axios-test
# 安装需要的axios依赖包
npm install axios
# 启动当前项目
npm run dev

成功启动前端项目后,当前项目的默认目录以及启动效果图如图所示:

axios-2.png

改动vue项目的主页面src/App.vue,测试使用axios发送HTTP请求。
改动后App.vue的代码如下:

<template>
  <div id="app">
    <img src="./assets/logo.png">
    <router-view/>
    <div v-for="item in studentList" :key="item.id">
      {{item.id}} - {{item.name}}
    </div>
  </div>
</template>

<script>
import axios from 'axios'
export default {
  name: 'App',
  data () {
    return {
      studentList: []
    }
  },
  created () {
    axios.get('localhost:8090/springboot_jpa/student/findAll').then(res => {
      this.studentList = res.data
      console.log(res)
    }).catch(err => {
      console.log(err)
    })
  }
}
</script>

上面这段代码的意思非常简单,就是定义了一个div,里面的信息就是初始化钩子created中使用axios去请求后端接口获取到学生信息列表。控制台会报错如下:

axios-3

其实这是跨域的问题,禁止跨域是浏览器的安全限制机制。解决的办法有很多种,这里采用的做法是,修改config/index.js文件的proxyTable属性如下:

proxyTable: {
  '/api': {
    target: 'http://localhost:8090',
    changeOrigin: true,
    pathRewrite: {
      '^/api': '/'
    }
  }
}

然后还需要把所有的HTTP请求的域名以及端口号都替换为/api

这里的意思是:在开发环境中,检测到所有以/api开发的HTTP请求,都会把其URL重写。(如:这里的就是会把检测的HTTP请求的URL—/api/springboot_jpa/student/findAll重写为http://localhost:8090/springboot_jpa/student/findAll,再去请求真正的后端接口)。

修改好config/index.jssrc/App.vue后,重启项目。最终的效果图如下:

axios-4

从上面的效果图来看,使用axios发送HTTP请求非常简单。同样地,其他类型的HTTP请求也可以自行测试,这里就不再赘述,只做总结。

  1. 我们可以使用axios提供的get()/post()/put()/delete()方法发送相应类型的http请求;
  2. 方法参数的拼接取决于其位置,以?拼接在URL后的参数我们可以自己拼接也可以使用params进行组装,封装在请求体(后端需要加注解@RequestBody)中的参数,可以使用对象拼接也可以使用data进行组装。

基本所有的项目都需要做权限认证。使用axios可以非常简单的实现这一功能。

利用axios.interceptors拦截器能够拦截所有的HTTP请求。

  1. 将用户登录成功后返回的token保存到本地仓库localStorage中。
  2. 然后给出登录\登出以外的所有接口在请求头上都加上token
  3. 在登出时,去清除本地仓库localStorage中的token集合。

实现这一功能的整合的axiosUtil工具包代码如下:

import axios from 'axios'
import router from 'vue-router'

// 创建实例并设置超时时间
const http = axios.create({
  timeout: 10000
})

// 拦截所有HTTP请求
// request拦截器
http.interceptors.request.use(config => {
  if (config.url === '/api/login') {
    return config
  }
  // 除了登录以外的所有接口,都在请求头加上进行权限认证用的token
  if (localStorage.token) {
    config.headers.common['Authorization'] = localStorage.token
  }
  return config
}, error => {
  return Promise.reject(error)
})

// response拦截器
http.interceptors.request.use(res => {
  // 登出接口清除token
  if (res.url === '/api/logout') {
    localStorage.removeItem('token')
  }
  return res
}, error => {
  if (error.response) {
    switch (error.response.status) {
      // 如果是由于权限问题报错,重定向到登录页面,登录成功后会回到原始页面(不一定是主页)
      case 401:
        router.replace({
          path: '/login',
          query: { redirect: router.currentRoute.fullPath }
        })
    }
  }
  return Promise.reject(error)
})

export default http

需要注意的是response拦截器是拿不到响应返回的数据的。也就是说,调用后端登录接口返回的token无法在response拦截器中获取。这样就只能在登录页面导入axiosUtil工具包,成功调用登录接口后再将token保存到localStorage中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章