Contents
8. Vue的异步请求¶
8.1. 1.前后端的交互模式¶
8.1.1. 1.1 接口调用方式¶
原生ajax
基于jQuery的ajax
fetch
axios
8.1.2. 1.2 传统形式的URL¶
/*
格式: schema://host:port/path?query#fragment
1. schema: 协议. 例如http,https,ftp等
2. host: 域名或者IP地址
3. port: 端口,http默认端口80,可以省略
4. path: 路径,例如/abc/a/b/c
5. query: 查询参数uname=list&age=12
6. fragment: 锚点(哈希Hash),用于定位页面某个位置
*/
8.1.3. 1.3 Restful形式的URL¶
/*
HTTP请求方式
1. GET 查询
2. POST 添加
3. PUT 修改
4. DELETE 删除
*/
8.1.4. 1.3 传统js异步调用¶
异步调用分析
/*
1. 定时任务
2. Ajax
3. 事件函数
*/
多次异步调用的依赖分析
/*
多次异步调用的结果顺序不确定
异步调用结果如果存在依赖需要嵌套
*/
8.2. 2.axios的安装与使用¶
axios是一个基于promise的HTTP库,主要用来向服务器端发起请求,可以在请求中做更多的可控操作,例如拦截请求等。
axios可以在浏览器和Node.js中使用,Vue、React等前端框架的广泛普及,促使了axios这种轻量级库的出现。
axios的特性:
(1)可以在浏览器中发送XMLHttpRequests。
(2)可以在Node.js中发送HTTP请求。
(3)支持Promise API。
(4)拦截请求和响应。
(5)转换请求数据和响应数据。
(6)能够取消请求。
(7)自动转换JSON数据。
(8)客户端支持保护安全,免受XSRF攻击。
8.2.1. 2.1 安装axios¶
正常情况下使用脚手架vue-cli创建的项目都集成了axios插件,无需安装,
如果需要安装请使用:
$ npm install axios --save
打开使用Vue脚手架创建的项目,在main.js文件中引入axios模块,代码如下:
import axios from 'axios'
createApp(App).prototype.$axios = axios
在组件中使用axios发送异步请求,代码如下:
<script>
export default {
mounted() {
this.axios.get('/user?id=123').then(ret=>{
console.log(ret.data)
})
}
}
</script>
8.2.2. 2.2 axios基本用法¶
axios主要的作用是向服务器端发起HTTP请求,根据HTTP标准,HTTP请求可以使用多种请求方法。
为了在开发中能够更方便地使用axios,axios为所有支持的请求方法提供了别名。
params 是将与请求一起发送的 URL 参数,对应后台中的query
data 是作为请求主体被发送的数据,对应后台中的body
axios.request(config)
axios.get(url[, config]) // 只支持 params 传参
axios.delete(url[, config]) // 只支持 params传参
axios.head(url[, config]) // 只支持 params传参
axios.post(url[, data[, config]]) // 同时支持 data 和 params
axios.put(url[, data[, config]]) // 同时支持 data 和 params
axios.patch(url[, data[, config]]) // 同时支持 data 和 params
注意 在使用别名方法时,url、method、data这些属性都不必在配置中指定。
axios常见5种请求方法
1.get请求
用于获取数据。
2.post请求
用于提交数据(新建)、包括表单提交及文件上传。
3.put请求
用于更新数据(修改),将所有数据都推送到后端。
4.patch请求
用于更新数据(修改),只将修改的数据推送到后端。
5.delete请求
用于删除数据。
1.get请求¶
GET请求用于获取数据,从指定的资源请求数据,并返回实体主体,代码如下:
<script>
import axios from 'axios'
export default {
name: 'get请求',
components: {},
created() {
//写法一
axios.get('接口地址', {
params: {
id: 12,//请求参数
},
}).then(
(res) => {
//执行成功后代码处理
}
)
//写法二
axios({
method: 'get',//请求方法
params: {
id: 12,//请求参数
},
url: '后台接口地址',
}).then(res => {
//执行成功后代码处理
})
}
}
</script>
2.post请求¶
POST请求是向指定资源提交数据并处理请求(例如提交表单或者上传文件)。
数据被包含在请求体中。POST请求一般分为两种类型:
form-data表单提交(图片上传,文件上传)
applicition/json
1)applicition/json请求方式代码如下:
<script>
import axios from 'axios'
export default {
name: 'post请求',
components: {},
created() {
//写法一
let data={
id:12
}
axios.post('接口地址', data}).then(
(res) => {
//执行成功后代码处理
}
)
//写法二
axios({
method: 'post',//请求方法
data: data,
url: '后台接口地址',
}).then(res => {
//执行成功后代码处理
})
}
}
</script>
2)formData请求方式代码如下:
<script>
import axios from 'axios'
export default {
name: 'post请求',
components: {},
created() {
//写法一
let data = {
id:12
}
let formData = new formData()
for(let key in data){
fromData.append(key,data[key])
}
axios.post('接口地址', fromData}).then(
(res) => {
//执行成功后代码处理
}
)
//写法二
axios({
method: 'post',//请求方法
data: fromData,
url: '后台接口地址',
}).then(res => {
//执行成功后代码处理
})
}
}
</script>
3.put请求¶
PUT请求用于更新数据,从客户端向服务器传送的数据取代指定的文档内容,代码如下:
<script>
import axios from 'axios'
export default {
name: 'put请求',
components: {},
created() {
//写法一
let data = {
id:12
}
axios.put('接口地址', data}).then(
(res) => {
//执行成功后代码处理
}
)
//写法二
axios({
method: 'put',//请求方法
data: data,
url: '后台接口地址',
}).then(res => {
//执行成功后代码处理
})
}
}
</script>
4.patch请求¶
PATCH请求也被用于更新数据,是对put方法的补充,用来对已知资源进行局部更新,代码如下:
<script>
import axios from 'axios'
export default {
name: 'patch请求',
components: {},
created() {
//写法一
let data = {
id:12
}
axios.patch('接口地址', data}).then(
(res) => {
//执行成功后代码处理
}
)
//写法二
axios({
method: 'patch',//请求方法
data: data,
url: '后台接口地址',
}).then(res => {
//执行成功后代码处理
})
}
}
</script>
5.delete请求¶
DELETE请求服务器删除指定的页面。使用axios发送DELETE请求,参数可以使用明文的方式或者封装对象的方式进行提交,代码如下:
<script>
import axios from 'axios'
export default {
name: 'delete请求',
components: {},
created() {
// 写法一
let data = {
id:12
}
//url传递参数
axios.delete('接口地址', {
parmas:{
id:12
}
}).then(
(res) => {
//执行成功后代码处理
}
)
// 写法二
//post方式传递参数
axios.delete('接口地址', {
data:{
id:12
}
}).then(
(res) => {
//执行成功后代码处理
}
)
}
}
</script>
8.2.3. 2.3 axios实例¶
当axios要请求多个不同的后端接口地址,并且一些axios配置项都相同时,可以先创建axios实例,然后使用axios实例发起请求。
项目中只有一个域名的情况
可以使用自定义配置新建一个axios实例,代码如下:
1、先在项目目录的src文件夹下创建一个utils的文件夹,并新建一个request.js的文件
const instance = axios.create({
baseURL:'http://localhost:8080',
timeout:1000,
})
//axios实例的用法
instance.get('/data.json').then(res=>{
console.log(res)
})
axios实例常用配置:
//常用的基本配置
axios.create({
baseURL:'http://localhost:8080', //请求的域名,基本地址
timeout:5000, //请求的超时时长,单位毫秒
url:'/data.json', //请求的路径
method:'get,post,put,patch,delete' , //请求方法
headers:{
token:'' //比如token登录鉴权,请求的时候携带token,让后端识别登录人的信息
}, //请求头
params:{}, //请求参数拼接在URL上
data:{}, //请求参数放在请求体里
})
baseURL设置:
let baseURL;
if(process.env.NODE_ENV === 'development') {
baseURL = 'xxx本地环境xxx';
} else if(process.env.NODE_ENV === 'production') {
baseURL = 'xxx生产环境xxx';
}
// 实例
let instance = axios.create({
baseURL: baseURL,
...
})
1.axios全局配置¶
// axios.defaults.后边跟的就是axios的那些配置
// 一般修改的全局配置,也就下边这两个
axios.defaults.timeout = 1000
axios.defaults.baseURL = 'http://localhost:8080'
2.axios实例配置¶
//如果create()里不添加参数的话,在创建这个axios实例的时候,使用的就是全局的配置
// 这个时候instance的timeout是1000
const instance = axios.create();
//如果设置了全局的配置,但是又想在创建的实例里修改配置怎么办
instance.default.timeout = 3000
axios请求配置
let instance = axios.create();
instance.get('/data.json',{
timeout:5000
})
修改实例配置的三种方式
// 第一种:局限性比较大
axios.defaults.timeout = 1000;
axios.defaults.baseURL = 'xxxxx';
// 第二种:实例配置
let instance = axios.create({
baseURL: 'xxxxx',
timeout: 1000, // 超时,401
})
// 创建完后修改
instance.defaults.timeout = 3000
// 第三种:发起请求时修改配置、
instance.get('/xxx',{
timeout: 5000
})
这3种配置方法的优先级是 axios请求配置 > axios实例配置 > axios全局配置
8.2.4. 2.4 axios并发请求¶
axios提供了并发请求的方法,可以同时进行多个请求,并统一处理返回值,代码如下:
<script>
import axios from 'axios'
export default {
created() {
axios.all([
axios.get('https://cnodejs.org/api/v1/topics'),
axios.get('https://cnodejs.org/api/v1/topics')
]).then(
axios.spread((res1, res2) => {
console.log(res1.data);
console.log(res2.data);
})
)
}
}
</script>
或如下代码
function getUserAccount() {
return axios.get('/user/12345');
}
function getUserPermissions() {
return axios.get('/user/12345/permissions');
}
axios.all([getUserAccount(), getUserPermissions()])
.then(axios.spread(function (acct, perms) {
// 两个请求现在都执行完成
}));
8.2.5. 2.5 axios拦截器¶
axios提供了拦截器功能,使用拦截器可以提高请求的可控性,并且完成更多复杂的操作。axios的拦截器分为请求拦截器和响应拦截器,两种拦截器在不同的时机对axios发起的请求进行处理。
// 请求拦截器
instance.interceptors.request.use(req=>{}, err=>{});
// 响应拦截器
instance.interceptors.reponse.use(req=>{}, err=>{});
1.请求拦截器¶
在请求被then或catch处理前拦截它们,代码如下:
// use(两个参数)
axios.interceptors.request.use(req => {
// 在发送请求前要做的事儿
...
return req
}, err => {
// 在请求错误时要做的事儿
...
// 该返回的数据则是axios.catch(err)中接收的数据
return Promise.reject(err)
})
2.响应拦截器¶
在响应被then或catch处理前拦截它们,代码如下:
// use(两个参数)
axios.interceptors.reponse.use(res => {
// 请求成功对响应数据做处理
...
// 该返回的数据则是axios.then(res)中接收的数据
return res
}, err => {
// 在请求错误时要做的事儿
...
// 该返回的数据则是axios.catch(err)中接收的数据
return Promise.reject(err)
})
3.常见错误码处理(error)¶
axios请求错误时,可在catch里进行错误处理。
axios.get().then().catch(err => {
// 错误处理
})
但实际开发过程中,一般在请求/响应拦截器中统一做错误处理,有特殊接口的话做单独的catch错误处理
4.axios取消请求处理¶
axios取消请求主要用于取消正在进行的HTTP请求,代码如下:
let source = axios.CancelToken.source();
axios
.get("/data.json", {
cancelToken: source.token
})
.then(res => {
console.log(res);
}).catch(err=>{
console.log(err)
})
// 取消请求(参数msg)
source.cancel('自定的的字符串可选')
8.2.6. 3.axios请求拦截器的案例¶
案例1
<script>
import axios from 'axios'
export default {
created() {
//请求拦截
axios.interceptors.request.use(config => {
// 发生请求前的处理
console.log(config)
return config;
}, err => {
// 请求错误处理
return Promise.reject(err);
});
//响应拦截
axios.interceptors.response.use(res => {
//请求成功对响应数据做处理
//该返回对象会传到请求方法的响应对象中
console.log(res)
return res
}, err => {
// 响应错误处理
return Promise.reject(err);
});
//发送请求
axios.get('https://cnodejs.org/api/v1/topics').then(res => {
console.log(res.data);
}, err => {
console.log(err);
})
}
}
</script>
案例2
src/utils/request.js的文件进行封装如下:
示例
import axios from 'axios'
import { Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'
import router from '@/router'
// create an axios instance
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
// withCredentials: true, // send cookies when cross-domain requests
timeout: 60000 // request timeout
})
// request interceptor
service.interceptors.request.use(
config => {
// do something before request is sent
if (store.getters.token) {
// let each request carry token
// ['X-Token'] is a custom headers key
// please modify it according to the actual situation
config.headers['Authorization'] = 'Bearer ' + getToken()
}
return config
},
error => {
// do something with request error
// console.log(error) // for debug
return Promise.reject(error)
}
)
// response interceptor
service.interceptors.response.use(
/**
* If you want to get http information such as headers or status
* Please return response => response
*/
/**
* Determine the request status by custom code
* Here is just an example
* You can also judge the status by HTTP Status Code
*/
response => {
const res = response.data
// if the response status is not 200, it is judged as an error.
if (res.errmsg) {
Message({
message: res.errmsg,
type: 'error',
duration: 1500
})
return Promise.reject(res.errmsg)
} else {
return res
}
},
error => {
if (!error.response) {
Message({
message: '系统错误!',
type: 'error',
duration: 1500
})
return Promise.reject(error)
}
if (error.response.status === 401) {
Message({
message: '登录失败,请重新登录!',
type: 'error',
duration: 1500
})
store.dispatch('user/resetToken').then(() => {
router.push({ path: '/login' })
})
} else if (error.response.status === 403) {
Message({
message: '您没有权限执行此操作!',
type: 'error',
duration: 1500
})
} else if (error.response.status === 400) {
Message({
message: '参数错误!',
type: 'error',
duration: 1500
})
} else if (error.response.status === 406) {
Message({
message: '当前状态只允许对应用户修改!',
type: 'error',
duration: 1500
})
} else if (error.response.status === 423) {
Message({
message: '对象被锁定,无法执行此操作!',
type: 'error',
duration: 1500
})
} else if (error.response.status > 499) {
Message({
message: '服务器内部错误',
type: 'error',
duration: 1500
})
} else {
Message({
type: 'error',
message: error.response.data.detail
})
}
return Promise.reject(error.response.data.detail)
}
)
export default service
8.3. 4. 封装axios¶
参考文献
8.4. 5. 基于接口的案例¶
/*
图书相关的操作基于后台接口数据进行操作
需要调用接口的功能点
1. 图书列表数据加载 GET http://localhost:3000/books
2. 添加图书 POST http;//localhost:3000/books
3. 验证图书名称是否存在 GET http://localhost:3000/books/book/:name
4. 编辑图书-根据ID查询图书信息 GET http://localhost:3000/books/:id
5. 编辑图书-提交图书信息 PUT http://localhost:3000/books/:id
6. 删除图书 DELETE http://localhost:3000/books/:id
*/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<link rel="stylesheet" type="text/css" href="css/index.css">
</head>
<body>
<div id="app">
<div class="grid">
<div>
<h1>图书管理</h1>
<div class="book">
<div>
<label for="id">
编号:
</label>
<input type="text" id="id" v-model='id' disabled="false" v-focus>
<label for="name">
名称:
</label>
<input type="text" id="name" v-model='name'>
<button @click='handle' :disabled="submitFlag">提交</button>
</div>
</div>
</div>
<div class="total">
<span>图书总数:</span>
<span>{{total}}</span>
</div>
<table>
<thead>
<tr>
<th>编号</th>
<th>名称</th>
<th>时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr :key='item.id' v-for='item in books'>
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.date | format('yyyy-MM-dd hh:mm:ss')}}</td>
<td>
<a href="" @click.prevent='toEdit(item.id)'>修改</a>
<span>|</span>
<a href="" @click.prevent='deleteBook(item.id)'>删除</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript" src="js/axios.js"></script>
<script type="text/javascript">
/*
图书管理-添加图书
*/
axios.defaults.baseURL = 'http://localhost:3000/';
axios.interceptors.response.use(function(res){
return res.data;
}, function(error){
console.log(error)
});
Vue.directive('focus', {
inserted: function (el) {
el.focus();
}
});
Vue.filter('format', function(value, arg) {
function dateFormat(date, format) {
if (typeof date === "string") {
var mts = date.match(/(\/Date\((\d+)\)\/)/);
if (mts && mts.length >= 3) {
date = parseInt(mts[2]);
}
}
date = new Date(date);
if (!date || date.toUTCString() == "Invalid Date") {
return "";
}
var map = {
"M": date.getMonth() + 1, //月份
"d": date.getDate(), //日
"h": date.getHours(), //小时
"m": date.getMinutes(), //分
"s": date.getSeconds(), //秒
"q": Math.floor((date.getMonth() + 3) / 3), //季度
"S": date.getMilliseconds() //毫秒
};
format = format.replace(/([yMdhmsqS])+/g, function(all, t) {
var v = map[t];
if (v !== undefined) {
if (all.length > 1) {
v = '0' + v;
v = v.substr(v.length - 2);
}
return v;
} else if (t === 'y') {
return (date.getFullYear() + '').substr(4 - all.length);
}
return all;
});
return format;
}
return dateFormat(value, arg);
})
var vm = new Vue({
el: '#app',
data: {
flag: false,
submitFlag: false,
id: '',
name: '',
books: []
},
methods: {
handle: async function(){
if(this.flag) {
// 编辑图书
var ret = await axios.put('books/' + this.id, {
name: this.name
});
if(ret.status == 200){
// 重新加载列表数据
this.queryData();
}
this.flag = false;
}else{
// 添加图书
var ret = await axios.post('books', {
name: this.name
})
if(ret.status == 200) {
// 重新加载列表数据
this.queryData();
}
}
// 清空表单
this.id = '';
this.name = '';
},
toEdit: async function(id){
// flag状态位用于区分编辑和添加操作
this.flag = true;
// 根据id查询出对应的图书信息
var ret = await axios.get('books/' + id);
this.id = ret.id;
this.name = ret.name;
},
deleteBook: async function(id){
// 删除图书
var ret = await axios.delete('books/' + id);
if(ret.status == 200) {
// 重新加载列表数据
this.queryData();
}
},
queryData: async function(){
// 调用后台接口获取图书列表数据
// var ret = await axios.get('books');
// this.books = ret.data;
this.books = await axios.get('books');
}
},
computed: {
total: function(){
// 计算图书的总数
return this.books.length;
}
},
watch: {
name: async function(val) {
// 验证图书名称是否已经存在
// var flag = this.books.some(function(item){
// return item.name == val;
// });
var ret = await axios.get('/books/book/' + this.name);
if(ret.status == 1) {
// 图书名称存在
this.submitFlag = true;
}else{
// 图书名称不存在
this.submitFlag = false;
}
}
},
mounted: function(){
// var that = this;
// axios.get('books').then(function(data){
// console.log(data.data)
// that.books = data.data;
// })
// axios.get('books').then((data)=>{
// console.log(data.data)
// this.books = data.data;
// })
this.queryData();
}
});
</script>
</body>
</html>