Fetch对象的简单使用
前言
百度二面的时候面试官叫我简单的基于fetch
封装一个简单的函数
没用过这个API所以当时直接说没用过,然后改成用XMLHttpRequest
来封装…
所以写一写来学习下基本的使用
fetch
fetch
可以看成XMLHttpRequest
的新版本,本质上解决的问题是一样的,就是使得网页能够通过js来发送请求和处理响应
可以在控制台打印下,发现它是一个内建函数
通过查看MDN,发现fetch
函数有两个参数
input
一个请求地址字符串或者一个Request
对象config
一个配置的对象,我觉得比较常用的是以下method
: 请求使用的方法headers
: 请求的头信息body
: 请求的 body 信息credentials
: 是否携带cookies
更多参数可以上MDN查看:WorkerOrGlobalScope.fetch()
并且fetch
返回的是一个Promise
,这也是他和XHR的重要的区别之一,
在XHR中,使用绑定事件的回调函数来获取数据,而在fetch
中,使用Promise
ok,可以来试下这个API的简单使用了
(PS:服务器的话使用我们之前的哪个Koa的服务器即可)
服务器端
1 | router.get('/hello', async ctx => { |
客户端
1 | // 因为这个访问的就是同域下的hello的接口,所以可以省略地址前缀 |
可以看到返回了一个Response
对象,里面有些字段也在XHR里面也有
比如:status
(状态码)、statusText
(状态码对应的解释文字)
不过没有看见数据,只看见了一个body
的属性,我们点开发现不是一个简单的对象,而是一个ReadableStream
对象
一个可读的流,基本上可以断定数据就在里面了,如何取出呢?
经过在MDN上的查找,需要通过ReadableStream
对象获取一个Reader
,
然后再在这个Reader
上面读取数据
1 | fetch("/hello", { |
执行后可以看到如下
Uint8Array
存放的是无符号的8位的整形数组,我们需要把里面的数据转成字符串然后拼接起来
1 | fetch("/hello", { |
执行之后就可以看到hello world!
当然这个解析其实有点问题,因为String.fromCodePoint
按照字符对应的unicode
编码来进行解析的
这对于0 - 127
的ASCII
码没有影响,因为UTF-8
对0 - 127
的编码和unicode
码完全一样
但是对于比如说中文,或者emoji表情就不是这样了,UTF-8
支持变长编码,通过前缀区分,此时编码和UTF-8表示的二进制就完全不一样了
比如这个笑哭的emoji表情 -> 😂 ,他的unicode
编码是11111011000000010
(2进制),转为16进制为1f602
,
但是它在UTF-8中的2进制编码为11110000 10011111 10011000 10000010
,和它的unicode
编码不一样
如果直接解析,那么会出现乱码,如下图
服务器代码
1 | router.get('/hello', async ctx => { |
乱码现象
可以看到uint8Array数组最后四个10进制数字分别对应了上面写到的😂
这个表情的UTF-8
的2进制编码
解决办法其实不难,就是把UTF-8
编码的转为unicode
码点即可
UTF-8
使用了变长的编码技术,通过前缀1的个数来判断字符占几个字节
Unicode编码(十六进制) | UTF-8 字节流(二进制) |
---|---|
000000-00007F | 0xxxxxxx |
000080-0007FF | 110xxxxx 10xxxxxx |
000800-00FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
010000-10FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
那么根据这个表进行解析就OK了,代码如下
1 | function utf8ToUnicode(utf8Array) { |
然后我们可以尝试对😂试一试,结果如下
正确地解析了出来
当然,自带的方法中已经很贴心地为我们解决了这个问题,通过调用text
方法即可(还是要懂原理的,这个才是重点)
1 | // const req = ... |
表情可以正常地显示了,也就是正常地解析出来了
Request
和Response
这两者是fetch
中经常会看到的对象
Request
fetch
的第一个参数可以传入一个Request
对象
Request
构造函数的参数和fetch
基本一样
也就是上面的代码可以改写成
1 | const req = new Request('/hello',{ |
运行之后能够得到和上面一样的结果
既然可以,那么我们就要看看到底是谁优先级更高了(刨根问底😂)
1 | const req = new Request('/hello',{ |
发现发出的是POST
请求,也就是说fetch
参数的优先级比Request
对象参数的优先级高(由于接口是GET
的,所以这里报错)
一个可能还不够证明,我们试试设置一些自定义的请求头
1 | const req = new Request('/hello',{ |
发现依然fetch
参数的优先级比Request
对象参数的优先级高
Response
fetch
返回的Promise
的解决结果对象
除了前面说过的text
方法,还有一些其他的方法方便开发者进行数据地转换
json
把数据流转换成json
格式,由于js天生支持json
格式的对象,所以很多时候都是使用这个办法formData
把数据转换成FormData
类型blob
把数据流转成Blob
类型arrayBuffer
把数据流转成ArrayBuffer
类型
一般用到json
方法会比较多,我们可以试一下
服务器代码
1 | router.get("/hello", async ctx => { |
客户端代码
1 | const req = new Request("/hello", { |
效果图
当然,使用blob
方法也能得到相同的结果
1 | const req = new Request("/hello", { |
效果图
这么做显然没必要,所以使用json
方法直接转换即可,blob
方法比如在下载,播放媒体这些比较常用
像B站使用的就是blob
的视频流
使用URL.createObjectURL(blob)
就可以使得blob对象以一种url
的形式展示出来,就可以在文档上通过src
来引用了
Headers
fetch
也支持以一个Headers
对象来设置请求头
常见的方法有
append
添加一个请求头delete
删除一个请求头entries
返回所有请求头的迭代器对象get
获取某个请求头的值has
判断是否含有某个请求头
可以改下之前的例子
1 | // 使用Headers对象 |
可以看见请求头都正确地添加到请求上面了
然后试试在控制台上操作一个header
的其他方法
后记
fetch
的兼容性还是相当不错的(除了IE,IE全版本不支持,😂)
虽然兼容性不错,但是基本上会使用一些上层的库二次地封装,比如axios
(基于XMLHttpRequest
)
所以很多时候用不到…
不过学还是要学的嘛