The article contains the knowledge of axios…
HTTP请求交互的基本过程
HTTP请求报文
请求行
多个请求头
Cookie是由后台产生,交给浏览器存储,浏览器发请求的时候会自动携带Cookie(浏览器利用Cookie请求头携带Cookie数据)
像这样的就是携带了两个Cookie
请求体
请求体数据格式有多种,可能是json格式,可能是urlencoded格式
像这一种是urlencoded格式:
我们需要告诉服务器我们请求体中的数据的类型服务器才能给我们进行解析,那么怎么告诉呢? 通过请求头中的Content-Type
如果Content-Type中是application/x-www-form-urlencoded 那么就是告诉服务器我请求体中的数据是这种格式:
同理json
HTTP响应报文
这里的Content-Type:
text/html表示返回的是html格式的文本,如果写了text/json表示返回的是json格式的文本,后面的charset=utf-8表示字符集编码为utf-8。
Set-Cookie是响应报文携带Cookie的方式,刚才说了请求报文是通过Cookie携带的cookie,而响应报文是通过Set-Cookie携带的cookie。
响应体有可能是html文本,也有可能是json文本,也有可能是js、css、图片等。
例子:
这里的响应体是css文本
post请求体参数格式
通常写在url后面的就是urlencoded格式的请求体参数:
如果是用于文件上传请求的那么他的格式就是multipart/form-data
常见响应状态码
不同类型的请求及其作用
API的分类
非rest风格的一个请求路径只对应一个操作,他一般是这么写的:…/getxxx/…表示获取、…/deletexxx/…表示删除,就是一眼就能看出来是什么操作,他一般GET做查询,POST做增删改。
使用json-server搭建REST API
直接去GitHub搜索json-server
我们打开一个工程,想要查看项目依赖了哪些包,去package.json看,如上图,dependencies是指依赖声明,在这个工程中我们依赖了两个包,一个是cors,一个是express
使用方法:
现在根目录下创建db.json,并写上如下代码:
之后在终端执行代码:
–watch表示监视db.json文件
注意,使用query参数查询它是通过过滤去查的(查询结果是一个数组)
而使用在url后面加斜杠id号的方式查询它是直接定位到该对象的(查出来的不是数组,是一个对象)
axios访问测试
首先从BootCDN上引入axios
get
post
put
delete
XHR
前端可以从URL获取数据,而无需让整个页面刷新
XHR可以发送ajax请求,与服务器交互,可以使得web页面只更新页面的局部而不影响用户操作
一般http请求与ajax请求
ajax请求由ajax引擎发送,一般请求由一般引擎发送。
ajax引擎请求拿到数据之后不会立刻更新,会由ajax引擎调用回调函数操作DOM再去局部更新。
一般http请求和ajax请求发的时候有点不一样,收的时候进一步处理也不一样。
API
XMLHttpRequest()
创建XHR对象的构造函数
status
相应状态码,只读的
statusText
响应状态文本(200对应的ok、404对应的not found等)
readyState(重要)
0:初始
1:open()之后
2:send()之后
3:请求中
4:请求完成
显示4表示请求完成,但是这个时候还不代表成功,要再去看status,status在一定范围内才表示成功
onreadystatechange
一个监听器,监听readyState值是否改变,当readyState值改变了这个监听器就会被触发。
一但是监听那就是函数,而且是回调函数
responseType
指定响应数据的类型,它关系到后面的response响应体数据的内容,如果指定了responseType,比方说就指定了json,那么response中的响应体数据就会自动解析json文本变为对象或者数组,而如果没有指定,则返回的响应体数据就是一段json文本,需要我们手动解析,类似于python的json.loads(),在js中用JSON.parse()
response
响应体数据,类型取决于responseType的指定
timeout
指定请求超时事件,默认为0代表没有限制
ontimeout
绑定超时的监听
由于是监听器,所以是函数而且是回调函数
onerror
绑定请求网络错误的监听
open()
初始化一个请求,参数为:(method, url[, async])
后面那个async一般可以不用指定,因为我们发送ajax请求一般都是异步的,但是如果一定要用同步(sync),其实影响到的是下面的send(data),如果是异步的,发一个请求立即结束,如果是同步的,需要等到当前请求返回数据之后再结束,可想而知,当请求多的时候,异步优势大
send(data)
发送请求
abort()
中断请求
发送请求,还没结束之前中断请求
例子:
我发了一个a请求和一个b请求,但是a请求迟迟不结束,而我需要的其实是b请求的数据,那么这个时候可以先判断a是否结束,如果a没有结束可以中断a请求,从而让b请求继续获取到数据
getResponseHeader(name)
获取指定名称的响应头值
不管是响应头还是请求头都有一个name对应一个value,并且可能有多对这样的name和value,这个函数显然就是拿到name对应的value
getAllResponseHeaders()
获取所有响应头组成的字符串
setRequestHeader(name, value)
设置请求头
由于请求头数据是浏览器端设置好之后发送给服务器端的,所以有设置请求头这么一个方法,而响应头是服务器端设置好之后返回给浏览器的,所以响应头数据只需要读,因此没有设置响应头一说
分析axios请求函数
上图axios括号里面的对象叫做配置对象,那么配置对象跟一般的对象有什么区别呢?配置对象中的key名称都是固定的,而且意义也是固定的,有哪些名字可以指定文档中会告诉我们,比方说这个method: ‘post’,意思就是指定请求的方式为post,显然我们不能写成method1 : ’post’。
配置对象中的属性有一个的名称叫做option,属性还有一个统一的名称叫做options(包含多个配置选项的对象)
Request Config
上图是axios中post的方法,下面看一下get的方法:
这里的params事实上就是拼在url路径后面的query参数
Response Schema
response返回的参数
注意:里面的data应该是解析以后的对象或数组,而不应该是json数据
当我们的axios请求成功的时候我们会需要返回一个response,这个时候就需要参考response schema了,下面那个案例就讲了怎么构建请求成功时的response
XHR的ajax封装(自制简单版axios)
案例
首先封装一下axios函数
注意,这里还没有写处理params和data的代码,下面我们会写的,先运行一下看看能不能发送请求。
1、GET
2、POST
下面我们来写处理data的代码
注意,有的后台只支持urlencoded格式,不支持json格式的请求体参数,后面会说怎么处理
需要注意的是axios括号里面配置对象的写法,这里不是写 “ :”了,而是写 “=” ,等到调用的时候又要写“ :”。
下面我们来写处理params的代码
相当于最后的url会变成:url?id=1&xxx=abc
我们可以用别的库帮我们自动实现url字符串拼接,当然我们也可以自己来写
绑定状态改变的监听
注意,这个状态改变监听是可以放在send后面的(当然放send前面那是完全没问题),那是因为send是异步的,而这个监听是同步的,基础班有讲异步是放在同步后面执行的。
由于readyState为0、1、2、3的时候说明请求还没走完是不需要处理的,因此直接return跳过
注意开头的第一句话:
所以resolve()里面需要传入response,reject()里面需要传入error
按照上面Response Schema格式,我们创建response,并传入resolve():
JSON.parse()操作就相当于实现了响应json数据自动解析为 js对象或数组
同样reject()里面需要传入error对象,error对象里面附上message
写完之后去button的函数中调用就可以
成功:
失败:
PUT请求
注意,这里url后面如果不写数字1就表示更新全部;
method中的put可以小写,只需要处理一下这个method,让他大写就行了
然后需要在原先已经处理了GET和POST请求的基础下再加一个处理PUT的请求即可
DELETE请求
在原先已经处理了GET、PUT和POST请求的基础下再加一个处理DELETE的请求即可
POST、DELETE和PUT的跨域请求(非重点)
注意,每一次发送POST或者PUT请求的时候都会有两个请求,第一个请求中的request method为OPTIONS,注意看他的response headers,里面一堆access-control-allow,这个其实是跨域请求(在上图这堆参数表示是允许跨域的)
我们这里是本地的绝对域,请求任何地址都是跨域的,但是我们发现竟然没出现跨域问题,能正常收到数据,说明他已经解决了跨域问题
原理:
浏览器出于安全考虑会先发一个预检请求(问一下服务器允不允许跨域,后台如果允许跨域了会返回一个响应头,也就是上图的上图的有那一堆access-control-allow的响应头),这个时候我就可以跨域访问了。而且如果我点很多次按钮:
会发现他不是每次都会发这个预检请求,他会隔一段时间发一次(换言之这是浏览器自己做的事情)
那为什么就只有GET没有这个预检请求呢?那是因为GET只是看看,不修改数据
axios的理解和使用
-
基于promise的异步ajax请求库,说明发送请求之后返回的是一个promise对象
-
拦截器(重点)
其实axios还有别的特点,比方说上图的最后一点,预防XSRF网络攻击
axios常用语法
spread()和all()这两个是配合使用的
语法糖
上文中axios有两种发请求的方式,一种是axios.get()一种是axios({method: “get”}),post、put等同理,那么axios.get()其实是一种语法糖
此时的axios是作为对象在使用
(create)创建一个axios实例用于定制
实例的方法
Config Defaults
比如这里的Global axios defaults是用于指定公共的一些属性,比方说baseURL,指定之后url就不需要写前面的这一部分了,再比如headers.post[‘Content-Type’]指定了之后后续就不需要再指定Content-Type属性了
另外还有很多公共配置项,具体看文档
Interceptors拦截器(重点★)
分为请求的拦截器和响应的拦截器,请求拦截器是在发请求之前执行,响应拦截器是在得到响应之后执行
处理错误(指定错误时的回调函数)
Cancellation(取消请求)
axios代码
这里我们发现传上去的数据默认写的就是json,但是我们并没有指定post的数据格式为json,这是因为在这个后台服务器中,当我们传入的数据为对象类型,他自动就会用json发,但是有的后台服务器它不支持json,后面会细说。
上图还可以这么写:
难点语法的理解和使用
后面项目中会讲axios的二次封装,因为axios已经是用xhr对ajax请求的一次封装了,所以这里说二次。
create
为啥会有create语法的产生呢?
场景:
有多台主机,我们需要从端口3000、4000的两台主机中取数据,又不想写两遍只有url不一样的axios,这个时候就轮到create发挥作用了:
我们可以用写了url端口为3000的axios去create一个端口为4000的axios实例,这样就达到了只改变url而不需要重复写其他多于代码的目的
当然,生成的instance也可以当作对象使用:
axios处理链流程
Interceptors拦截器
添加请求拦截器
还能指定多个,我们在第一个拦截器下面再写一个:
添加响应拦截器
这里我们也来两个:
之后发送get请求:
拦截结果:
我们发现是先执行第二个请求拦截器再执行第一个,先执行第一个响应拦截器再执行第二个
请求拦截器在get等请求之后执行,响应拦截器在处理响应以及处理error之前执行,那是因为可能有多个axios去发请求,那么拦截器肯定是处理公共部分的拦截的,所以肯定是在每一个axios请求处理自己的响应以及error之前去执行这个公共的响应拦截器,而且必须是在get等请求发出之后去执行请求拦截器,否则没有意义。
就比如上图就有两个axios请求,那么响应拦截器和请求拦截器会在get之后,then(response=>{})之前进行拦截。
需要注意的是,拦截器中都需要return:
如果有一个没有return就会报undefined,那是因为每一个拦截器都相当于一个任务,发请求也是一个任务,我们需要把这所有的任务都串联起来,每一环要去接上一环return过来的数据的,如果有一环没有return,那下一环拿到的从上一环来的数据就是undefined。
而且我们讲拦截器是在get等请求发生之后,在then(response=>{})之前进行的拦截,那比方说这个response没有return出去,最终就会导致then(response=>{})里面的response为undefined,那最终的结果就会是undefined)
再比如没有return error,这个就比较严重了,由于没有返回error,下一步程序会走到成功的那一块里面去,也就是原本程序下一步会走到下一步的错误处理块里面,但却走到了正确处理的那一块程序里面,显然这是错误的。
这其实是Promise里面的机制,去看看Promise的源码就会比较清楚了。
取消请求
首先要new一个CancelToken对象
注意如果用axios.get的方式去写的话他的其他属性的写法格式是怎么写的(是在url后面的大括号里面写的),参照上图
执行器(new的内部的函数)
注意,对象里面传进去了一个函数,这样的函数也叫做执行器,Promise里面也有类似的函数,也就是在new对象的时候同步执行,注意是同步执行,也就是说她在new的内部会立即执行,也就是说这个函数是个回调函数,而且上图这个函数还传了一个形参,我们来看看这个形参怎么用:
他到外面去调用了,说明这是一个函数类型的形参
注意这个函数类型的形参现在还不能调用,因为一旦调用了,她会立即取消请求,所以我们把它保存起来(上图的全局变量cancel就是用于保存的),在之后再调用
那么我们在什么时候用呢?
应该在按取消按钮的时候用,那么我们就把他写到取消请求的函数里面去,而且考虑到用户可能没有发送请求就直接去按取消按钮了,那么这个时候CancelToken还没有new出来:
也就是没有经历上图这一步
那在这个时候如果去调用cancel()函数(这个时候cancel为null)显然是会报错的,所以我们做出改进:
先判断cancel是否是一个函数(如果是说明已经发送请求)
但是还有一个问题,当请求发出后已经结束了或者出了某些问题导致结束,这个时候再去按取消请求按钮是没有意义的,所以我们应该在请求结束以及出错处理块里面将cancel置为null
结果:
我们发现当取消请求的时候程序事实上跳到error块里面去处理了,也就是进入了失败的流程
而且这个error还是一个特别的对象,这里我们输出error
发现他不是一般的Error对象,而是axios给我们提供的Cancel对象,里面有message属性,但是没有stack属性(那是因为他不是一个Error对象,我们讲Error对象才真正的有message和stack属性)。也就是说我们现在命名的是error,但是他不是一般的Error对象:
那么什么情况下他会是Error对象呢?
在发送错误请求的时候会是Error对象:
我们发现返回的是Error对象,而且打印的是stack信息(上图红圈圈部分)
取消请求比较常用的方式
我们在点击一个按钮之后又点击了另一个按钮,但是上一个按钮的事件还没有执行完毕,这个时候我们希望能取消上一个按钮的请求
只需要在请求的按钮事件最前面写上上述代码即可
但是这样会有一个小问题:
我们会发现实际测试的时候有些成功被取消了而有些没有
我们知道取消请求之后她会进入错误流程,也就是会进入error块,这个时候需要区别这个错误到底是Error类型(普通错误)还是Cancel类型(被取消请求事件触发的错误)
axios.isCancel()
用于判断错误类型是否是Cancel类型
当我们这样写之后,程序就会对这两种错误区别对待,这个时候就能解决上面那个小问题了
error是回调函数,异步执行
那么为什么区别对待两种错误之后程序就正常了呢?
那是因为error它是一个异步的回调函数,异步的特点就是会在同步后面执行,现在从头开始走一遍流程:
-
我发送请求1;
-
我立即发送请求2,要取消请求1;
-
点击请求2按钮后首先触发cancel()函数,跳到了请求1的error回调函数里面,但还没有执行;
-
请求2赋值新的cancel;
-
执行error回调函数将cancel置为null;
-
再次点击请求2或者请求1,由于cancel已经被置空,这个时候不会执行cancel()函数,也就是上面的请求2不会被取消,导致了bug
所以这么一分析它的原因就在cancel=null这句话上,
所以对于Cancel行为,我们不要将cancel置为空:
出现error之后继续传递error
发生错误之后如果我们想要传递这个错误的话则需要在error块最后return 一个错误Promise或者throw这个error;而如果到这里就结束了那就不用return,一般情况下到这就完了,就不用return了
拦截器的使用
我们发现上面的代码极其繁琐,重复度高,其实可以用拦截器来做优化
添加请求拦截器
可以把cancelToken这个属性写成拦截器中的config的属性,如上图
添加响应拦截器
分析一下,取消请求的错误其实不需要处理,真正需要向下传递的其实是一般的错误,所以我们在响应拦截器中关于Cancel行为的error直接中断就行了
注意上图我们是如何中断Cancel类型的error的,首先判断是否是Cancel,是的话进去直接通过 return new Promise(()=>{})去中断(上面我们手写过Promise,如果里面传递了一个空的函数的话就相当于传入了一个空的Promise,该Promise啥都不处理自然就中断了),如果是一般错误就直接return Promise.reject(error)
上图是写完拦截器之后的axios请求函数,可以看到是非常简洁的,而且这里的error只处理一般的错误不处理Cancel行为的错误
技巧
不懂就看GitHub上面的axios文档