JavaScript 学习笔记 - 网络请求与远程资源(四)
from 《JavaScript 高级程序设计》第四版 第24章 网络请求与远程资源
--------------------------------------------------------------------------------------------------------------------
一、Fetch API
1. 基本用法
fetch('bar.txt').then((response) => { // 请求完成、资源可用时,期约会解决为一个Response 对象
console.log(response); // Response { type: "basic", url: ...}
response.text().then((data) => { // text() 方法返回一个期约,会解决为取得资源的完整内容,纯文本格式的内容
console.log(data);
}
});
// 推荐使用以下写法,更简洁明了
fetch('bar.txt')
.then((response) => response.text())
.then((data) => console.log(data));
2. 处理状态码和请求失败
只要服务器返回了响应,fetch() 期约都会解决。至于真正的“成功”请求,则需要在处理响应时再定义。
因为服务器没有响应而导致浏览器超时,这样真正的fetch() 失败会导致期约被拒绝
fetch('/hangs-forever')
.then((response) => {
console.log(response.status); // 200 or 404 or 500...
console.log(response.statusText); // OK or Not Found or Internal Server Error
console.log(response.ok); // true or false
}, (err) => {
console.log(err);
});
// (浏览器超时后)
// TypeError: "NetworkError when attempting to fetch resource."
3. 自定义选项
只使用URL 时,fetch() 会发送 GET 请求,只包含最低限度的请求头。要进一步配置如何发送请求,需要传入可选的第二个参数 init 对象。使用 init 对象参数,可以配置 fetch() 在请求体中发送各种序列化的数据。
4. 常见 Fetch 请求模式
1)发送 JSON 数据
let payload = JSON.stringify({
foo: 'bar'
});
let jsonHeaders = new Headers({
'Content-Type': 'application/json'
});
fetch('/send-me-json', {
method: 'POST', // 发送请求体时必须使用一种HTTP方法
body: payload,
headers: jsonHeaders
});
2)在请求体中发送参数
// 因为请求体支持任意字符串值,所以可以通过它发送请求参数
let payload = 'foo=bar&baz=qux';
let paramHeaders = new Headers({
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
});
fetch('/send-me-params', {
method: 'POST',
body: payload,
headers: paramHeaders
});
3) 发送文件
因为请求体支持 FormData 实现,所以 fetch() 也可以序列化并发送文件字段中的文件:
let imageFormData = new FormData();
let imageInput = document.querySelector("input[type='file']");
imageFormData.append('image', imageInput.files[0]);
fetch('/img-upload', {
method: 'POST',
body: imageFormData
});
这个 fetch() 实现可以支持多个文件
let imageFormData = new FormData();
let imageInput = document.querySelector("input[type='file'][multiple]");
for (let i = 0; i < imageInput.files.length; ++i) {
imageFormData.append('image', imageInput.files[i]);
}
fetch('/img-upload', {
method: 'POST',
body: imageFormData
});
4)加载 Blob文件
const imageElement = document.querySelector('img');
fetch('my-image.png')
.then((response) => response.blob())
.then((blob) => {
imageElement.src = URL.createObjectURL(blob);
});
5)发送跨源请求
6)中断请求
Fetch API 支持通过 AbortController/AbortSignal 对中断请求。调用 AbortController.abort() 会中断所有网络传输,特别适合希望停止传输大型负载的情况。中断进行中的 fetch() 请求会导致包含错误的拒绝。
let abortController = new AbortController();
fetch('wikipedia.zip', {signal: abortController.signal})
.catch(() => console.log('aborted!');
// 10毫秒后中断请求
setTimeOut(() => abortController.abort(), 10);
// 已经中断
二、Headers 对象
Request.prototype.headers, Response.prototype.headers,另外,使用 new Headers() 也可以创建一个新实例。Headers对象与 Map对象极为相似。都有 get()、set()、has() 和 delete() 等实例方法。当然 Headers 并不是与 Map处处都一样,在初始化 Headers对象时,也可以使用键/值对形式的对象,而Map 则不可以;Headers 对象还可以通过 append() 方法添加多个值。
三、Request 对象
通过fetch 使用 Request会将请求体标记为已使用。也就是说,有请求体的 Request只能在一次 fetch中使用。要想基于包含请求体的相同 Request对象多次调用 fetch(),必须在第一次发送 fetch() 请求前调用 clone() :
let r = new Request('https://foo.com', { method: 'POST', body: 'foobar'});
// 3个都会成功
fetch(r.clone());
fetch(r.clone());
fetch(r);
四、Response 对象
有响应体的 Response对象只能读取一次。(不包含响应体的Response 对象不受此限制)比如:
let r = new Response('foobar');
r.text().then(console.log); // foobar
r.text().then(console.log);
// TypeError: Failed to execute 'text' on 'Response': body stream is locked
要多次读取包含响应体的同一个 Response对象,必须在第一次读取前调用 clone():
let r = new Response('foobar');
r.clone().text().then(console.log); // foobar
r.clone().text().then(console.log); // foobar
r.text().then(console.log); // foobar
五、Request、Response及 Body 混入
Request 和 Response 都使用了 Fetch API 的 Body混入,以实现两者承担有效载荷的能力。这个混入为两个类型提供了只读的body 属性(实现为 ReadableStream)、只读的 bodyUsed 布尔值(表示body 流是否已读)和一组方法,用于从流中读取内容并将结果转换为某种 JavaScript 对象类型。
通常,将 Request 和 Response 主体作为流来使用主要有两个原因。一个原因是有效载荷的大小可能会导致网络延迟,另一个原因是流 API 本身在处理有效载荷方面是有优势的。除此之外,最好是一次性获取资源主体。
Body 混入提供了5个方法,用于将 ReadableStream 转存到缓冲区的内存里,将缓冲区转换为某种 JavaScript 对象类型,以及通过期约来产生结果。在解决之前,期约会等待主体流报告完成及缓冲被解析。这意味着客户端必须等待响应的资源完全加载才能访问其内容。
1. Body.text()
fetch('https://foo.com')
.then((response) => response.text())
.then(console.log);
2. Body.json()
fetch('https://foo.com/foo.json')
.then((response) => response.json())
.then(console.log); // {"foo": "bar"}
3. Body.formData()
fetch('https://foo.com/form-data')
.then((response) => response.formData())
.then((formData) => console.log(formData.get('foo'));
4. Body.arrayBuffer()
fetch('https://foo.com')
.then((response) => response.arrayBuffer()).
.then(console.log); // ArrayBuffer(...) { }
5. Body.blob
fetch('https://foo.com')
.then((response) => response.blob())
.then(console.log); // Blob(...) {size:..., type: "..."}
6. 一次性流
因为 Body 混入是构建在ReadableStream 之上的,所以主体流只能使用一次。这意味着所有主体混入方法都只能调用一次,再次调用就会抛出错误。
7. 使用 ReadableStream 主体