10分钟了解fetch
2024-12-09 11:13:16

朋友:也许面试官是问你什么是fetch呢?我:什么是fetch?

正文

如果不是刷面试题,我真的不知道有fetch这个东西。

FETCH

基本历史

远古时期,XMLHttpRequest对象的出现,JavaScript调用它就可以让浏览器异步地发http请求,然后这项异步技术就被称为Ajax。

之后jQuery封装了它,让异步结果更清晰的表现在一个对象的回调函数属性上,编写方式更简单,但出现了新的问题,回调地狱。

Promise为了解决异步编程的回调地狱问题诞生了。

随后有人把XHR对象用Promise封装了起来,它就是axios库(浏览器端),axios在node.js环境是http模块的封装。

后来又出现了一个可以异步地发http请求的api,就是fetch()。

Fetch它并非是封装xhr对象的库,而是全新的JavaScript的接口。

而且Fetch的api天生就是自带Promise的,现在的Ajax就有了两种方式: XHR对象和Fetch()。

Ajax,Axios,Fetch三者关系

  1. Ajax 是一种代表异步 JavaScript + XML 的模型(技术合集),所以 Fetch 也是 Ajax 的一个子集
  2. 在之前,我们常说的 Ajax 默认是指以 XHR 为核心的技术合集,而在有了 Fetch 之后,Ajax 不再单单指 XHR 了,我们将以 XHR 为核心的 Ajax 技术称作传统 Ajax
  3. Axios 属于传统 Ajax(XHR)的子集,因为它是基于 XHR 进行的封装。
1
现代AJAX包含XHR和Fetch两种类型的常见接口请求方式,AJAX和AXIOS都是基于XHR做的封装。

基本使用

fetch()接受一个 URL 字符串作为参数,默认向该网址发出 GET 请求,返回一个 Promise 对象

基本用法如下:

1
2
3
4
5
6
7
8
9
fetch(url)
.then(...)
.catch(...)

// 查询参数直接通过 ?、& 拼接
fetch('http://example.com/movies.json')
.then(response => response.json())
.then(json => console.log(json))
.catch(err => console.log('Request Failed', err))

fetch()接收到的response是一个 Stream 对象response.json()是一个异步操作,取出所有内容,并将其转为 JSON 对象

使用 await 语法改写:

1
2
3
4
5
6
7
8
9
async function getJSON() {
let url = 'http://example.com/movies.json'
try {
let response = await fetch(url)
return await response.json()
} catch (error) {
console.log('Request Failed', error)
}
}

配置参数

使用示例

fetch()的第一个参数是 URL,还可以接受第二个参数,作为配置对象,定制发出的 HTTP 请求

  • fetch(url, options)
  • post、put、patch 用法类似
  • HTTP 请求的方法、标头、数据体都在options这个对象里面设置

下面是一些示例:

(1)POST 请求

1
2
3
4
5
6
7
8
9
const response = await fetch(url, {
method: 'POST',
headers: {
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8",
},
body: 'foo=bar&lorem=ipsum'
})

const json = await response.json()

此处的body指的是POST 请求的数据体

(2)提交 JSON 数据

1
2
3
4
5
6
7
8
const user =  { name:  'John', surname:  'Smith'  }
const response = await fetch('/article/fetch/post/user', {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
body: JSON.stringify(user)
})
1
Content-Type`的默认值是`'text/plain;charset=UTF-8'

(3)提交表单

1
2
3
4
5
const form = document.querySelector('form')
const response = await fetch('/users', {
method: 'POST',
body: new FormData(form)
})

(5)直接上传二进制数据

fetch()也可以直接上传二进制数据,将 Blob 或 arrayBuffer 数据放在body属性里面

1
2
3
4
5
let blob = await new Promise(resolve => canvasElem.toBlob(resolve,  'image/png'))
let response = await fetch('/article/fetch/post/image', {
method: 'POST',
body: blob
})

完整配置项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const response = fetch(url, {
// 指定请求方法
method: "GET",
// 指定请求头
headers: {
"Content-Type": "text/plain;charset=UTF-8"
},
// 指定请求体数据
body: undefined,
// 指定 fetch() 请求的 referer 标头
referrer: "about:client",
// 指定 Referer 标头的规则
referrerPolicy: "no-referrer-when-downgrade",
// 指定请求的模式
mode: "cors",
// 指定是否发送 Cookie
credentials: "same-origin",
// 指定如何处理缓存
cache: "default",
// 指定 HTTP 跳转的处理方法
redirect: "follow",
// 指定一个哈希值,用于检查 HTTP 回应传回的数据是否等于这个预先设定的哈希值
integrity: "",
// 用于页面卸载时,告诉浏览器在后台保持连接,继续发送数据
keepalive: false,
// 指定一个 AbortSignal 实例,用于取消fetch()请求
signal: undefined
})

由于官网对于部分配置项没有中文翻译,以下列出全部配置项具体值及其说明

cache:指定如何处理缓存,可能的取值如下:

  • default:默认值,先在缓存里面寻找匹配的请求
  • no-store:直接请求远程服务器,并且不更新缓存
  • reload:直接请求远程服务器,并且更新缓存
  • no-cache:将服务器资源跟本地缓存进行比较,有新的版本才使用服务器资源,否则使用缓存
  • force-cache:缓存优先,只有不存在缓存的情况下,才请求远程服务器
  • only-if-cached:只检查缓存,如果缓存里面不存在,将返回504错误

mode:指定请求的模式,可能的取值如下:

  • cors:默认值,允许跨域请求
  • same-origin:只允许同源请求
  • no-cors:请求方法只限于 GET、POST 和 HEAD,并且只能使用有限的几个简单标头,不能添加跨域的复杂标头,相当于提交表单所能发出的请求

credentials:指定是否发送 Cookie,可能的取值如下:

  • same-origin:默认值,同源请求时发送 Cookie,跨域请求时不发送
  • include:不管同源请求,还是跨域请求,一律发送 Cookie
  • omit:一律不发送

redirect:指定 HTTP 跳转的处理方法,可能的取值如下:

  • follow:默认值,fetch()跟随 HTTP 跳转
  • error:如果发生跳转,fetch()就报错
  • manualfetch()不跟随 HTTP 跳转,但是response.url属性会指向新的 URL,response.redirected属性会变为true,由开发者自己决定后续如何处理跳转

referrerPolicy:用于设定Referer标头的规则,可能的取值如下:

  • no-referrer-when-downgrade:默认值,总是发送Referer标头,除非从 HTTPS 页面请求 HTTP 资源时不发送
  • no-referrer:不发送Referer标头
  • originReferer标头只包含域名,不包含完整的路径
  • origin-when-cross-origin:同源请求Referer标头包含完整的路径,跨域请求只包含域名
  • same-origin:跨域请求不发送Referer,同源请求发送
  • strict-originReferer标头只包含域名,HTTPS 页面请求 HTTP 资源时不发送Referer标头
  • strict-origin-when-cross-origin:同源请求时Referer标头包含完整路径,跨域请求时只包含域名,HTTPS 页面请求 HTTP 资源时不发送该标头
  • unsafe-url:不管什么情况,总是发送Referer标头

取消Fetch请求

fetch()请求发送以后,如果中途想要取消,需要使用AbortController对象,流程如下:

  • 创建AbortController实例
  • 配置对象的signal属性指定接收AbortController实例发送的信号controller.signal
  • 使用controller.abort()方法发出取消信号
  • 发出取消信号后,会触发abort事件,这个事件可以监听,也可以通过controller.signal.aborted属性判断取消信号是否已经发出
1
2
3
4
5
6
7
8
9
10
11
let controller = new AbortController()
let signal = controller.signal

fetch(url, {
signal: signal
})

signal.addEventListener('abort', () => console.log('abort!'))

controller.abort()// 取消fetch请求
console.log(signal.aborted); // true

Response对象

fetch()请求成功以后,得到的是一个 Response 对象。它对应服务器的 HTTP 响应

1
const response = await fetch(url)

实例属性

Response 实例属性如下表:

属性 返回值 含义
ok 布尔值 表示请求是否成功,true对应 HTTP 请求的状态码 200 到 299,false对应其他的状态码
status 数字 表示 HTTP 回应的状态码(如:200,表示成功请求)
statusText 字符串 表示 HTTP 回应的状态信息(如:请求成功以后,服务器返回”OK”)
url 请求的 URL 如果 URL 存在跳转,该属性返回的是最终 URL
type 请求的类型 basic:普通请求,即同源请求 cors:跨域请求 error:网络错误,主要用于 Service Worker opaque:如果fetch()请求的type属性设为no-cors,就会返回这个值。表示发出的是简单的跨域请求 opaqueredirect:如果fetch()请求的redirect属性设为manual,就会返回这个值
redirected 布尔值 表示请求是否发生过跳转
body ReadableStream 对象 暴露响应体内容
headers 与响应关联的Headers对象 通过访问与响应关联的 Headers 对象,来操作 HTTP 响应头

fetch()发出请求以后,只有网络错误,或者无法连接时fetch()才会报错,其他情况都不会报错,而是认为请求成功。这意味着服务器返回的状态码是4xx5xx时,不会报错(Promise 不会变为 rejected状态)

以下两种方法可以判断是否发生错误:

  • 通过status属性,得到 HTTP 回应的真实状态码,判断请求是否成功
  • 判断ok属性是否为true

Response.body属性是 Response 对象暴露出的底层接口,返回一个 ReadableStream 对象,供用户操作

它可以用来分块读取内容,应用之一就是显示下载的进度

1
2
3
4
5
6
7
8
const response = await fetch('flower.jpg')
const reader = response.body.getReader()

while(true) {
const {done, value} = await reader.read()
if (done) { break }
console.log(`Received ${value.length} bytes`)
}

response.body.getReader()方法返回一个遍历器。这个遍历器的read()方法每次返回一个对象,表示本次读取的内容块,其中:

  • done属性是一个布尔值,用来判断有没有读完
  • value属性是一个 arrayBuffer 数组,表示内容块的内容
  • value.length属性是当前块的大小

Response 对象还有一个Response.headers属性,指向一个 Headers 对象,对应 HTTP 回应的所有标头

Headers 对象提供了以下方法,用来操作标头:

  • Headers.get():根据指定的键名,返回键值
  • Headers.has(): 返回一个布尔值,表示是否包含某个标头
  • Headers.set():将指定的键名设置为新的键值,如果该键名不存在则会添加
  • Headers.append():添加标头
  • Headers.delete():删除标头
  • Headers.keys():返回一个遍历器,可以依次遍历所有键名
  • Headers.values():返回一个遍历器,可以依次遍历所有键值
  • Headers.entries():返回一个遍历器,可以依次遍历所有键值对([key, value]
  • Headers.forEach():依次遍历标头,每个标头都会执行一次参数函数

方法具体用法请参考官网:developer.mozilla.org/zh-CN/docs/…

实例方法

Response对象根据服务器返回的不同类型的数据,提供了不同的读取方法

  • response.text():得到文本字符串
  • response.json():得到 JSON 对象
  • response.blob():得到二进制 Blob 对象
1
2
3
4
5
// 读取图片文件flower.jpg,显示在网页上
const response = await fetch('flower.jpg')
const myBlob = await response.blob()
const myImage = document.querySelector('img')
myImage.src = URL.createObjectURL(myBlob)
  • response.formData():得到 FormData 表单对象
  • response.arrayBuffer():得到二进制 ArrayBuffer 对象
1
2
3
4
5
6
7
8
9
10
const audioCtx = new window.AudioContext();
const source = audioCtx.createBufferSource();

const response = await fetch('song.ogg');
const buffer = await response.arrayBuffer();

const decodeData = await audioCtx.decodeAudioData(buffer);
source.buffer = buffer;
source.connect(audioCtx.destination);
source.loop = true;

Stream 对象只能读取一次,这意味着,前五个读取方法,只能使用一个,否则会报错。Response 对象提供了克隆方法

  • response.clone():创建Response对象的副本,实现多次读取
1
2
3
4
5
6
7
8
const response1 = await fetch('flowers.jpg')
const response2 = response1.clone()

const myBlob1 = await response1.blob()
const myBlob2 = await response2.blob()
// 将同一张图片读取了两次
image1.src = URL.createObjectURL(myBlob1)
image2.src = URL.createObjectURL(myBlob2)

结语

fetch这种方法虽然才了解,但是感觉好像还行,看了很多文章介绍,感觉在文件读取这里似乎有不错的应用。

但是说到底,这终归不是工作中常用的,连面试题问到的似乎都很少,如果不是这里刷到,似乎都没什么人讨论。

参考

一文熟悉Ajax

有同学问我:Fetch 和 Ajax 有什么区别? - 掘金 (juejin.cn)