# 网络请求-Fetch
# Fetch
JavaScript 可以将网络请求发送到服务器,并在需要时加载新信息。
例如,我们可以使用网络请求来:
- 提交订单,
- 加载用户信息,
- 从服务器接收最新的更新, ……等。 ……所有这些都没有重新加载页面!
对于来自 JavaScript 的网络请求,有一个总称术语 “AJAX”(Asynchronous JavaScript And XML 的简称)。虽然现在我们使用 JSON 多于使用 XML。由于这个术语诞生于很久以前,所以这个词一直在那儿。
基本语法:
let promise = fetch(url, [options]);
第一阶段,当服务器发送了响应头(response header),fetch 返回的 promise 就使用内建的 Response class 对象来对响应头进行解析。
第二阶段,为了获取 response body,我们需要使用一个其他的方法调用。
方法 | 作用 |
---|---|
res.text() | 读取 response,并以文本形式返回 |
res.json() | 将 response 解析为 JSON |
res.formData() | 读取 response,并以文本形式返回 |
res.blob() | |
res.arrayBuffer() |
TIP
注意 fetch 返回的是一个 promise,response 的方法返回的也是一个 promise
# 示例
blob 示例
let response = await fetch("/article/fetch/logo-fetch.svg");
let blob = await response.blob(); // 下载为 Blob 对象
// 为其创建一个 <img>
let img = document.createElement("img");
img.style = "position:fixed;top:10px;left:10px;width:100px";
document.body.append(img);
// 显示它
img.src = URL.createObjectURL(blob);
setTimeout(() => {
// 3 秒后将其隐藏
img.remove();
URL.revokeObjectURL(img.src);
}, 3000);
上面我们发送了 GET 请求,现在我们来发送 POST 请求
let user = {
name: "John",
surname: "Smith"
};
let response = await fetch("/article/fetch/post/user", {
method: "POST",
headers: {
"Content-Type": "application/json;charset=utf-8" // 注意发送JSON的时候设置"Content-Type"
},
body: JSON.stringify(user)
});
let result = await response.json();
alert(result.message);
发送图片可以直接使用 blob,而不需要专门设置"Content-Type",浏览器会自动处理
let blob = await new Promise((resolve) => canvasElem.toBlob(resolve, "image/png"));
let response = await fetch("/article/fetch/post/image", {
method: "POST",
body: blob
});
# Request header
要在 fetch 中设置 request header,我们可以使用 headers 选项。它有一个带有输出 header 的对象,如下所示:
let response = fetch(protectedUrl, {
headers: {
Authentication: "secret"
}
});
# Response header
Response header 位于 response.headers 中的一个类似于 Map 的 header 对象。
它不是真正的 Map,但是它具有类似的方法,我们可以按名称(name)获取各个 header,或迭代它们。
TIP
HTTP 报文的 header 是纯文本的形式,这不利于我们直接处理。因而浏览器的封装是有必要的额
# 总结
典型的 fetch 请求由两个 await 调用组成:
let response = await fetch(url, options); // 解析 response header
let result = await response.json(); // 将 body 读取为 json
或者以 promise 形式:
fetch(url, options)
.then(response => response.json())
.then(result => /* process result */)
# Fetch: progress
fetch 方法允许去跟踪 下载 进度。
要跟踪下载进度,我们可以使用 response.body 属性,response.body 给予了对进度读取的完全控制,我们可以随时计算下载了多少。
下面有一个比较脏的实现
// 代替 response.json() 以及其他方法
const reader = response.body.getReader();
// 在 body 下载时,一直为无限循环
while (true) {
// 当最后一块下载完成时,done 值为 true
// value 是块字节的 Uint8Array
const { done, value } = await reader.read();
if (done) {
break;
}
console.log(`Received ${value.length} bytes`);
}
这个实现我们可以改造成定时器或者 RAF 版本,不过暂时还是不折腾了。
# Fetch: abort
正如我们所知道的,fetch 返回一个 promise。JavaScript 通常并没有“中止” promise 的概念。那么我们怎样才能取消一个正在执行的 fetch 呢?例如,如果用户在我们网站上的操作表明不再需要 fetch。
为此有一个特殊的内建对象:AbortController。它不仅可以中止 fetch,还可以中止其他异步任务。
用法非常简单。
# AbortController 对象
创建一个控制器(controller):
let controller = new AbortController();
控制器是一个极其简单的对象。
它具有单个方法 abort(), 和单个属性 signal,我们可以在这个属性上设置事件监听器。
let controller = new AbortController();
let signal = controller.signal;
signal.addEventListener("abort", () => alert("abort!"));
controller.abort(); // 中止!
alert(signal.aborted); // true
# 与 fetch 一起使用
为了能够取消 fetch,请将 AbortController 的 signal 属性作为 fetch 的一个可选参数(option)进行传递
let controller = new AbortController();
fetch(url, {
signal: controller.signal
});
controller.abort();
cancelToken
我们常用的请求库也具备这项功能,比如 axios 的cancelToken
,可以类比上下两段代码,我们可以发现其用法惊人的一致。
const source = axios.CancelToken.source();
axios.get("/user/12345", {
cancelToken: source.token
});
source.cancel("Operation canceled by the user.");