使用Fetch API 探索前后端数据交互
前言
在当今的 Web 开发中,前端与后端的数据交互是构建动态应用的核心。API 是连接不同软件应用的重要桥梁,允许开发者通过 HTTP 请求与服务器交互,高效调用API数据对于构建现代 Web 应用至关重要。传统的页面刷新方式已经无法满足用户对流畅体验的需求,而 Fetch API 的出现为 JavaScript 带来了全新的生命力。
一、Fetch API 概述
1.1 Fetch API 是什么❓
Fetch API 是现代浏览器提供的一个用于发起网络请求的接口,用于发起 HTTP 请求。它提供简洁的异步API,使开发者能够以更现代的方式与服务器交互。它是传统的 XMLHttpRequest 的替代品,提供了更简洁、更强大的功能。基于 Promise 实现,使异步操作更加直观。
相比于传统的 XMLHttpRequest 更加强大、灵活且易于使用。Fetch 基于 Promise 设计,使得异步请求的处理更加优雅。
| 特性 | Fetch API | XMLHttpRequest |
|---|---|---|
| 语法 | 基于 Promise,更简洁 | 回调函数,较复杂 |
| 请求/响应对象 | 标准化 | 非标准化 |
| 默认携带 Cookie | 不携带 | 携带 |
| 超时控制 | 需要额外实现 | 原生支持 |
| 取消请求 | 使用 AbortController | 原生支持 |
| 进度事件 | 有限支持 | 完整支持 |
1.2 Fetch 的基本语法
Fetch API 的基本用法是通过调用 fetch() 函数并传入一个 URL 作为参数来发起网络请求。该函数返回一个Promise对象,可以在其then()方法中处理请求成功的情况,在catch()方法中处理请求失败的情况。Fetch API 最基本的形式如下所示。
fetch(url, options)
.then(response => response.json()) // 解析 JSON 数据
.then(data => console.log(data)) // 处理数据
.catch(error => console.error('出现错误:', error)); // 错误处理
上述代码示例展示了使用 Fetch API 发起一个请求,返回的 Promise 解析为响应对象,进而能访问响应体数据。处理响应体通常包含 JSON 数据,通过 .json() 方法解析。如果请求失败,fetch 返回的 promise 会拒绝,并将错误信息传给 catch 方法。
1.3 fetch 配置选项
fetch 接受第二个可选参数,一个可以控制不同配置的对象,常见属性如下表所示。
| 配置项 | 简要描述 | 常用值 |
|---|---|---|
| method | 请求的 HTTP 方法,默认方法为GET | GET、POST、PUT、PATCH、DELETE |
| headers | 请求中 HTTP 标头 | |
| body | 请求体。 请注意,使用 GET 和 HEAD 方法的请求不能有正文 |
|
| mode | 指定请求的模式。 | cors:默认值,允许跨域请求 same-origin:只允许同源请求。 no-cors:不能添加跨域的复杂标头,相当于提交表单所能发出的请求 |
| credentials | 指定是否发送 Cookie | same-origin:默认值,同源请求时发送 Cookie,跨域请求时不发送 include:不管同源请求,还是跨域请求,一律发送 Cookie omit:一律不发送 |
| cache | 指定如何处理缓存 | default:默认值,先在缓存里面寻找匹配的请求 no-store:直接请求远程服务器,并且不更新缓存 reload:直接请求远程服务器,并且更新缓存 no-cache: force-cache:缓存优先,只有不存在缓存的情况下,才请求远程服务器 only-if-cached:只检查缓存,如果缓存里面不存在,将返回504错误 |
| redirect | 如何处理 HTTP 重定向响应,默认设置为follow | follow、error、manual |
| referrer | 包含请求的反向链接的字符串,默认为空字符串 | |
| referrerPolicy | 指定用于请求的反向链接政策 | |
| signal | AbortSignal 对象实例,支持接口中止请求 | |
| priority | 指定当前请求相对于其他同类请求的优先级, 默认设置为auto |
high、low、auto |
二、Fetch API 的基本使用
Fetch API 支持多种 HTTP 请求方法,如GET、POST、PUT、DELETE等。默认情况下,fetch() 函数会发送 GET 请求。如果需要发送其他类型的请求,可以在fetch() 函数的第二个参数中指定请求的配置对象。
2.1 发起 GET 请求
GET 请求是最常见的请求类型,用于从服务器获取数据。在Fetch API中,构造一个 GET 请求的URL是一件非常简单的事情。首先需要了解的是,GET请求的参数通常是通过URL的查询字符串(query string)部分传递给服务器的。
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('出现错误:', error));
在上述例子中,fetch 执行 GET 请求,在构建URL时,需要确保查询参数是经过URL编码的,以避免查询字符串解析错误。一旦发起 GET 请求,就需要处理服务器返回的响应数据,Fetch API 返回的 response 是一个 Response 对象,可以使用以下任一方法获取响应内容:
| 方法 | 简要说明 |
|---|---|
| response.text() | 返回一个使用以文本为响应正文解析的 Promise |
| response.json() | 返回一个使用从 JSON 响应中解析的对象解析的 Promise |
| response.blob() | 返回一个使用以 Blob 对象为响应正文解析的 Promise |
| response.ArrayBuffer() | 返回一个使用以 ArrayBuffer 实例为响应正文解析的 Promise |
| response.formData() | 返回一个使用以 FormData 对象为响应正文解析的 Promise |
2.2 发起 POST 请求
POST 请求用于向服务器发送数据,如提交表单或调用 API 提交数据到服务器。通过 Fetch API 调用 POST 请求需要构造一个包含请求体的对象,并将这个对象作为第二个参数传递给 fetch 函数。
const userData = {
username: 'example',
email: 'example@example.com'
};
fetch('https://api.example.com/users', {
method: 'POST',
body: JSON.stringify(userData)
})
.then(response => response.json())
.then(data => console.log('Success:', data))
.catch(error => console.error('出现错误:', error));
使用 fetch() 发送 POST 请求的关键是指定要发送至服务器的数据,它可以采用多种格式,包括 JSON、FormData 和文本格式。在上述示例中,我们通过设置 method 为 POST 来发送 POST 请求,并在请求体 body 中发送 JSON 格式的数据。fetch 函数会将这些信息发送到服务器,并等待响应。
2.3 使用 async/await
Fetch API 支持 async/await 语法,可以更简洁地处理异步操作,如下所示。fetch 接收到的 response 是一个 Stream 对象,response.json() 是一个异步操作,取出所有内容,并将其转为 JSON 对象。
const response = await fetch(url, options);
const data = await response.json();
三、Fetch API 的响应处理
3.1 处理 HTPP 响应
fetch 请求成功以后,得到的是一个 Response 对象,它对应服务器的 HTTP 响应。
const res=await fetch(url)
Response 包含的数据通过 Stream 接口异步读取,但它还有一些同步属性,对应 HTTP 回应的标头信息(Headers),如下表所示。
| 标头属性 | 类型 | 简要说明 |
|---|---|---|
| Response.ok | boolean | 表示请求是否成功,true 对应的 HTTP 请求状态码200-299,false对应其他的状态码 |
| Response.status | number | 返回一个数字,表示HTTP响应的状态码 |
| Response.statusText | string | 表示HTTP响应的状态信息,例如请求成功以后,服务器返回 OK |
| Response.url | string | 返回请求的URL。如果URL存在跳转,该属性返回的是最终的URL |
| Response.type | string | 返回的是请求的类型。可能为以下值: basic:普通请求,即同源请求 cors:跨域请求 error:网络错误,主要用于Service Worker opaque:如果fetch请求的type属性为no-cors,就会返回这个值,表示发出的是简单的跨域请求 opaqueredirect:如果fetch请求的redirect属性设为manual,就会返回这个值 |
| Response.redirected | boolean | 表示请求是否有过重定向 |
3.2 处理请求响应状态
在处理请求响应时,我们首先检查响应状态是否成功(response.ok),如果不成功则抛出错误。fetch 发出请求后,只有网络错误或无法连接时才会报错,即使服务器返回的状态码 是4xx或5xx。只有通过 Response.status 属性得到 HTTP 响应的真实状态码时,才能判断请求是否成功。
fetch('https://api.example.com/data')
.then(response => {
if (!response.ok) {
throw new Error('网络请求错误,' + response.statusText);
}
return response.json();
})
.then(data => console.log('Success:', data))
.catch(error => console.error('出现错误:', error));
3.3 处理不同的响应类型
当接收到服务器的响应后,通常需要解析响应体。Response 对象根据服务器返回的不同类型的数据,提供了不同的读取方法。这几个方法都是异步的,返回的都是 Promise 对象。必须等到异步操作结束,才能得到服务器返回的完整数据。
| 方法 | 简要说明 |
|---|---|
| response.text() | 获取文本字符串,主要用于获取文本数据,比如 HTML 文件 |
| response.json() | 获取 JSON 对象,主要用于获取服务器返回的 JSON 数据 |
| response.blob() | 获取二进制 Blob 对象 |
| response.formData() | 获取 FormData 表单对象,主要用于拦截用户提交的表单,修改某些数据后再提交给服务器 |
| response.arrayBuffer() | 得到二进制 ArrayBuffer 对象,主要用于获取流媒体文件 |
Fetch API 可以处理多种响应格式:
// 处理JSON响应
fetch('/api/data.json')
.then(response => response.json())
.then(data => console.log(data));
// 处理文本响应
fetch('/api/data.txt')
.then(response => response.text())
.then(text => console.log(text));
// 处理Blob响应(如图片)
fetch('/image.png')
.then(response => response.blob())
.then(blob => {
const objectURL = URL.createObjectURL(blob);
document.getElementById('image').src = objectURL;
});
注意,Response 是一个 Stream 对象,而 Stream 对象只能读取一次,读取完就没了。这意味着,上面的几个读取方法,只能使用一个,否则会报错。
四、高级 Fetch 用法
4.1 设置请求头
在使用Fetch API进行请求时,可以通过 Headers 对象来设置请求头。每个请求或响应都有一个与之关联的 Headers 对象,这个对象包含了请求头和响应头,例如 Content-Type、Authorization 等。
fetch('https://example.com/api', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer your-token'
},
body: JSON.stringify({ name: 'John', age: 30 })
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('出现错误:', error));
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() | 依次遍历标头,每个标头都会执行一次参数函数 |
上面的有些方法可以修改标头,那是因为继承自 Headers 接口。有些标头不能通过headers属性设置,比如Content-Length、Cookie 、Host等等。它们是由浏览器自动生成,无法修改。这些方法中,最常用的是 response.headers.get(),用于读取某个标头的值。
let response = await fetch(url);
response.headers.get('Content-Type')
4.2 设置请求体参数
当需要发送POST请求时,经常需要向服务器发送一些数据。使用Fetch API可以很便捷地通过 body 属性发送请求体。
// 发送JSON数据
const data = JSON.stringify({ name: 'John', age: 30 });
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: data
};
fetch('https://example.com/api/users', options)
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
// 发送表单数据
const formdata = new FormData();
formdata.append('username', 'john');
formdata.append('email', 'john@example.com');
fetch('https://example.com/api/register', {
method: 'POST',
body: formdata
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
4.3 设置请求超时
Fetch API 本身不支持超时设置,但可以通过 AbortController 实现:
const controller = new AbortController();
const signal = controller.signal;
// 设置5秒超时
const timeoutId = setTimeout(() => controller.abort(), 5000);
fetch('https://api.example.com/data', { signal })
.then(response => response.json())
.then(data => {
clearTimeout(timeoutId);
console.log(data);
})
.catch(error => {
if (error.name === 'AbortError') {
console.log('Request timed out');
} else {
console.error('Other error:', error);
}
});
4.4 跨域请求
如果需要进行跨域请求,可以在服务器端设置 CORS(Cross-Origin Resource Sharing)。在前端,也可以通过 credentials 选项来指定是否发送 cookies 等凭据。
fetch('https://example.com/api', {
method: 'GET',
credentials: 'include' // 允许跨域请求时携带 cookie
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('出现错误:', error));
4.5 上传文件
如果表单里面有文件选择器,使用 Fetch 上传文件时,可以构造出一个表单,进行上传。
const fileInput = document.querySelector('input[type="file"]');
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('username', 'exampleUser');
fetch('https://api.example.com/upload', {
method: 'POST',
body: formData
// 注意:不要手动设置Content-Type头,浏览器会自动设置正确的boundary
})
.then(response => response.json())
.then(data => console.log('Upload success:', data))
.catch(error => console.error('Upload error:', error));
4.6 请求取消
使用 AbortController 取消正在进行的请求:
const controller = new AbortController();
// 开始请求
fetch('https://api.example.com/data', {
signal: controller.signal
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => {
if (error.name === 'AbortError') {
console.log('Request was aborted');
} else {
console.error('Error:', error);
}
});
// 在某个事件中取消请求
document.getElementById('cancel-button').addEventListener('click', () => {
controller.abort();
});
4.7 并发请求
使用 Promise.all 处理多个并发请求:
async function fetchMultipleResources() {
try {
const [usersResponse, postsResponse] = await Promise.all([
fetch('https://api.example.com/users'),
fetch('https://api.example.com/posts')
]);
if (!usersResponse.ok || !postsResponse.ok) {
throw new Error('One or more requests failed');
}
const users = await usersResponse.json();
const posts = await postsResponse.json();
console.log('Users:', users);
console.log('Posts:', posts);
// 合并数据并更新UI
displayCombinedData(users, posts);
} catch (error) {
console.error('Error fetching data:', error);
}
}
function displayCombinedData(users, posts) {
// 实现数据合并和显示逻辑
}
五、总结
Fetch API 是一个强大而简洁的网络请求 API,它基于 Promise 实现,提供了更好的可读性和可维护性。通过学习和掌握 Fetch API 的基本用法和请求方法,我们可以更轻松地发起网络请求并处理响应结果。同时,我们也需要注意 Fetch API 的一些限制和常见问题,并采取相应的措施来解决它们。











