Request 和 Response 都使用了 Fetch API 的 Body 混入
Request 和 Response 使用 Fetch API 的 Body 混入是什么意思?
在 Fetch API 中,"Body 混入"(Body Mixin)是一个重要的概念。
什么是混入(Mixin)
混入是一种面向对象编程的概念,允许将一个对象的属性和方法"混入"到另一个对象中,实现代码复用。
在 Fetch API 中,Body 混入提供了一组处理 HTTP 消息体的方法和属性。
这个混入为两个类型(Request 和 Response)提供了:
- 只读的 body 属性(实现为ReadableStream)
- 只读的 bodyUsed 布尔值(表示body流是否已读)
- 一组方法(用于从流中读取内容并将结果转换为某种JavaScript对象类型)
Body混入提供了5个方法,用于将ReadableStream转存到缓冲区的内存里,将缓冲区转换为某种JavaScript对象类型,以及通过期约来产生结果。
在解决之前,期约会等待主体流报告完成及缓冲被解析。
这意味着客户端必须等待响应的资源完全加载才能访问其内容。
Body 混入包含的属性和方法
1. body 属性 - 一个只读的 ReadableStream,表示响应/请求的内容
2. bodyUsed 属性 - 布尔值,表示 body 是否已被读取
3. arrayBuffer() 方法 - 读取 body 并返回 ArrayBuffer 格式的 Promise
4. blob() 方法 - 读取 body 并返回 Blob 格式的 Promise
5. formData() 方法 - 读取 body 并返回 FormData 格式的 Promise
6. json() 方法 - 读取 body 并返回 JSON 解析后的 Promise
7. text() 方法 - 读取 body 并返回文本格式的 Promise
为什么 Request 和 Response 都使用 Body 混入
1. 代码复用 - Request 和 Response 都需要处理 HTTP 消息体,使用相同的接口
2. 一致性 - 提供统一的 API 来处理请求体和响应体
3. 功能共享 - 无论处理请求还是响应,都可以使用相同的方法读取和解析数据
实际意义
- Request 对象可以使用 bodyUsed、json() 等方法处理请求体
- Response 对象可以使用 bodyUsed、json() 等方法处理响应体
- 两者共享相同的处理逻辑和 API
示例:Response 对象使用 Body 混入的方法
// 示例:Response 对象使用 Body 混入的方法
console.log('=== Response 对象的 Body 混入 ===');
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(response => {// 检查 Body 混入的属性console.log('Response body:', response.body); // ReadableStreamconsole.log('Response bodyUsed:', response.bodyUsed); // false (还未读取)// 使用 Body 混入的方法解析响应体return response.json();
})
.then(data => {console.log('解析后的数据:', data);
});// 示例:Request 对象使用 Body 混入的方法
console.log('=== Request 对象的 Body 混入 ===');
let requestWithBody = new Request('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
body: JSON.stringify({ title: 'test', body: 'test body', userId: 1 })
});console.log('Request body:', requestWithBody.body); // ReadableStream
console.log('Request bodyUsed:', requestWithBody.bodyUsed); // false (还未读取)// 克隆并读取 Request 体
let clonedRequest = requestWithBody.clone();
clonedRequest.json().then(data => {
console.log('Request body 数据:', data);
console.log('原 Request bodyUsed:', requestWithBody.bodyUsed); // false
console.log('克隆 Request bodyUsed:', clonedRequest.bodyUsed); // true (已被读取)
});// 示例:Body 混入的各种方法
console.log('=== Body 混入的各种方法 ===');// text() 方法 - 获取文本格式
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(response => response.text())
.then(text => {console.log('文本格式的响应:', text.substring(0, 50) + '...');
});// json() 方法 - 解析为 JSON
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(response => response.json())
.then(json => {console.log('JSON 格式的响应:', json);
});// blob() 方法 - 获取 Blob 对象
fetch('https://jsonplaceholder.typicode.com/photos/1')
.then(response => response.blob())
.then(blob => {console.log('Blob 格式的响应:', blob);console.log('Blob 类型:', blob.type);console.log('Blob 大小:', blob.size);
})
.catch(err => {// 图片接口可能跨域,这里捕获错误console.log('获取图片 Blob 失败(可能是因为跨域):', err.message);
});// arrayBuffer() 方法 - 获取 ArrayBuffer
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(response => response.arrayBuffer())
.then(buffer => {console.log('ArrayBuffer 格式的响应:', buffer);console.log('ArrayBuffer 长度:', buffer.byteLength);
});// formData() 方法 - 获取 FormData(适用于表单数据)
/*
// 这个示例需要服务器返回表单格式的数据
fetch('/form-handler', {
method: 'POST',
body: new FormData(document.querySelector('form'))
})
.then(response => response.formData())
.then(formData => {
for (let [key, value] of formData.entries()) {console.log(key, value);
}
});
*/// Body 只能被读取一次的特性
console.log('=== Body 只能被读取一次 ===');
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(response => {console.log('读取前 bodyUsed:', response.bodyUsed); // false// 第一次读取return response.text().then(text => {console.log('第一次读取结果长度:', text.length);console.log('第一次读取后 bodyUsed:', response.bodyUsed); // true// 尝试第二次读取会失败return response.text().catch(err => {console.log('第二次读取失败:', err.message); // "Body has already been consumed."});});
});// 正确处理多次读取的方法 - 使用 clone()
console.log('=== 使用 clone() 方法处理多次读取 ===');
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(response => {// 克隆响应对象let clone1 = response.clone();let clone2 = response.clone();// 可以分别读取不同的克隆Promise.all([response.text(), // 原始响应clone1.json(), // 克隆1解析为 JSONclone2.blob() // 克隆2解析为 Blob]).then(([text, json, blob]) => {console.log('文本格式长度:', text.length);console.log('JSON 格式标题:', json.title);console.log('Blob 格式大小:', blob.size);});
});