Fetch API
fetch api 是 XMLHttpRequest 的升级版
基本使用
与 XHR 相比,fetch有几大特点:
- Promise yes! 回调函数 no!
- 模块化设计,API分散在 Request 对象、Response 对象、Header对象等
- 通过数据流 Stream 处理数据,可以分块读取
js
// 链式调用风格
fetch(url)
.then((res) => res.text())
.then((res) => console.log("OK!!", res))
.catch((e) => console.log('Err!!', e))
// await 同步风格
async function getJSON() {
try {
let response = await fetch(url);
return await response.json();
} catch (error) {
console.log('Request Failed', error);
}
}Response 对象
fetch 方法的返回值是 Promise<Response>,Response 对象对应服务器的HTTP回应。
属性
Response 本身有一些请求头信息,也可进一步通过流式异步读取数据,具体的标头信息包括:
- ok
- status -- http 状态码
- statusText -- 200 OK 的 OK
- url -- 如果发生了重定向,拿到的就是最终 url
- type
- basic: 同源请求
- cors:跨域请求
- error:网络错误
- opace:跨域请求中的简单请求
- opaqueredirect
- redirected -- 返回是否发生过重定向
判断请求成功
javascript
fetch(url)
// 404, 500 等都是fulfilledPromise, 但res.ok只有 200~299 才是true
.then((res) => { console.log(res.status, res.ok); return res.text() })
.then((res) => console.log("OK!!", res))
.catch((e) => console.log('Err!!', e))
// 用户断网、域名无法解析、TCP错误等... 才会报错
const xhr = new XMLHttpRequest()
xhr.open('GET', url + '1')
xhr.onerror = () => {
// 用户断网、域名无法解析、TCP错误等... 才会报错
console.log('xhr err!!', xhr.status)
}
xhr.onload = () => {
// 404, 500 等都是走这里
console.log('xhr ok !!', xhr.status)
}
xhr.send()response header
headers 属性中包含了一个Header对象,其中具有所有的响应头消息,以及get set append delete 等方法。
js
fetch(url)
.then((res) => {
// for (let [key, value] of res.headers) {
// console.log(`${key} : ${value}`);
// }
console.log(res.headers.get('etag'))
console.log(res.headers.get('Content-Type'))
return res.text()
})内容获取
针对不同类型的数据,有多种读取方法
- .text()
- .json()
- .blob()
- .formData()
- 主要用在 Service Worker 里面,拦截用户提交的表单,修改某些数据以后,再提交给服务器
- .arrayBuffer()
clone
Stream 对象只能读取一次,读取完就没了。比如我把 res.json() 解析为 json 之后,我想再看看 .text() 的结果就不行了。
所以还有 clone 方法, 可以把 res.clone() 一份,然后任意解析
Response Body
Response.body属性是 Response 对象暴露出的底层接口,返回一个 ReadableStream 对象,供用户操作。
它可以用来分块读取内容,应用之一就是显示下载的进度。
javascript
// fetch 实现下载进度
const url = window.location.href + 'test'
const response = await fetch(url);
const reader = response.body.getReader();
const totalLength = response.headers.get('Content-Length')
console.log(totalLength)
let accumulateBytes = 0
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
// console.log(`Received ${value.length} bytes`)
accumulateBytes += value.length
console.log(`Progress :: ${Math.floor(accumulateBytes / totalLength * 100)}%`)
}上面示例中,response.body.getReader()方法返回一个遍历器。 read()读取 done用来判断有没有读完;value是一个 arrayBuffer 数组,表示内容块的内容
⛈ 锐评:
- 这东西其实不太实用,一般做下载就是通过浏览器原生下载来做,让文件从服务器经由浏览器直接到用户文件系统里。而不是放在js内存里。实现方式如下:
<a download>Content-Disposition: attachment
fetch 第二个参数
fetch(url, option)
option 的完整示例:
js
const response = fetch(url, {
method: "GET",
headers: {
"Content-Type": "text/plain;charset=UTF-8"
},
body: undefined,
referrer: "about:client",
referrerPolicy: "no-referrer-when-downgrade",
mode: "cors",
credentials: "same-origin",
cache: "default",
redirect: "follow",
integrity: "",
keepalive: false,
signal: undefined
});cache 属性
有以下那么几个,可以覆盖浏览器的默认行为:
- default:默认值,先在缓存里面寻找匹配的请求。
- no-store:直接请求远程服务器,并且不更新缓存。
- reload:直接请求远程服务器,并且更新缓存。
- no-cache:将服务器资源跟本地缓存进行比较,有新的版本才使用服务器资源,否则使用缓存。
- force-cache:缓存优先,只有不存在缓存的情况下,才请求远程服务器。
- only-if-cached:只检查缓存,如果缓存里面不存在,将返回504错误。
场景:服务器返回 Cache-Control: max-age=3600
- 默认行为(
cache: 'default'):1 小时内直接用缓存,不发请求。 - 使用
cache: 'reload':即使在 1 小时内,也发请求到服务器,并更新缓存。 - 使用
cache: 'force-cache':1 小时后(缓存过期),仍然用旧缓存,不发请求。 - 使用
cache: 'no-store':永远不读/写缓存,每次都拿最新内容。
credentials
credentials属性指定是否发送 Cookie,这也是覆盖浏览器默认的发送cookie行为
可能的取值如下:
same-origin:默认值,同源请求时发送 Cookie,跨域请求时不发送。include:不管同源请求,还是跨域请求,一律发送 Cookie。omit:一律不发送
signal
可以传入一个 AbortSignal 实例,取消 fetch 请求
js
// 1. 创建 AbortController 实例
const controller = new AbortController();
const signal = controller.signal;
// 2. 将 signal 传给 fetch
fetch('/api/data', { signal })
.then(res => res.json())
.then(data => console.log(data))
.catch(err => {
if (err.name === 'AbortError') { // !!!!
console.log('请求已被取消');
} else {
console.error('请求失败:', err);
}
});
// 3. 在需要时取消请求(比如用户点击“取消”按钮)
setTimeout(() => {
controller.abort(); // 触发 AbortError
}, 1000);