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)
所以很多时候用不到…
不过学还是要学的嘛