抛弃 Ajax:拥抱更简洁强大的 Fetch API
传统 Ajax 的繁琐实现
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
var response = JSON.parse(xhr.responseText);
console.log(response);
} else {
console.error('请求失败,状态码:', xhr.status);
}
}
};
xhr.onerror = function() {
console.error('请求出错');
};
xhr.send();
Fetch API 基础用法
基本 GET 请求
fetch('https://api.example.com/data')
.then(response => {
if (!response.ok) {
throw new Error('网络响应不正常');
}
return response.json();
})
.then(data => console.log(data))
.catch(error => console.error('请求出错:', error));
使用 async/await 语法
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error('获取数据失败:', error);
}
}
fetchData();
请求配置
POST 请求示例
fetch('https://api.example.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer your_token_here'
},
body: JSON.stringify({
name: 'John Doe',
email: 'john@example.com'
})
})
.then(response => response.json())
.then(data => console.log('Success:', data))
.catch(error => console.error('Error:', error));
发送 FormData
const formData = new FormData();
formData.append('username', 'john');
formData.append('avatar', fileInput.files[0]);
fetch('https://api.example.com/profile', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(result => console.log('Success:', result))
.catch(error => console.error('Error:', error));
高级功能
流式响应处理
Fetch API 提供了多种方法来处理不同类型的响应数据:
- JSON 数据 - 最常用的格式,适合 REST API
fetch('https://api.example.com/data.json')
.then(response => response.json())
.then(data => console.log(data));
- 文本数据 - 处理纯文本响应
fetch('https://api.example.com/data.txt')
.then(response => response.text())
.then(text => console.log(text));
- 二进制数据 (Blob) - 处理图像、文件等
fetch('https://api.example.com/image.png')
.then(response => response.blob())
.then(blob => {
const imgUrl = URL.createObjectURL(blob);
document.getElementById('myImg').src = imgUrl;
});
- ArrayBuffer - 处理原始二进制数据
fetch('https://api.example.com/data.bin')
.then(response => response.arrayBuffer())
.then(buffer => {
// 处理二进制数据
const view = new DataView(buffer);
console.log(view.getInt32(0));
});
- FormData - 处理表单数据响应
fetch('https://api.example.com/form')
.then(response => response.formData())
.then(formData => {
console.log(formData.get('username'));
});
中断请求
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('请求被中止');
} else {
console.error('请求出错:', error);
}
});
并行请求
Promise.all([
fetch('https://api.example.com/users'),
fetch('https://api.example.com/posts')
])
.then(async ([usersResponse, postsResponse]) => {
const users = await usersResponse.json();
const posts = await postsResponse.json();
return { users, posts };
})
.then(data => console.log('Combined data:', data))
.catch(error => console.error('Error in one of the requests:', error));
错误处理最佳实践
async function fetchWithErrorHandling(url, options = {}) {
try {
const response = await fetch(url, options);
if (!response.ok) {
// 尝试解析错误信息
let errorInfo;
try {
errorInfo = await response.json();
} catch (e) {
errorInfo = await response.text();
}
// 根据状态码抛出不同类型的错误
if (response.status >= 400 && response.status < 500) {
throw new ClientError(response.status, errorInfo);
} else if (response.status >= 500) {
throw new ServerError(response.status, errorInfo);
}
}
return await response.json();
} catch (error) {
if (error instanceof DOMException && error.name === 'AbortError') {
console.log('请求被用户中止');
throw error;
}
console.error('请求失败:', error);
throw error;
}
}
class ClientError extends Error {
constructor(status, message) {
super(`客户端错误: ${status} - ${message}`);
this.status = status;
}
}
class ServerError extends Error {
constructor(status, message) {
super(`服务器错误: ${status} - ${message}`);
this.status = status;
}
}
// 使用示例
fetchWithErrorHandling('https://api.example.com/data')
.then(data => console.log(data))
.catch(error => console.error('处理后的错误:', error));
实际应用示例
封装可复用的 HTTP 客户端
class HttpClient {
constructor(baseURL = '', headers = {}) {
this.baseURL = baseURL;
this.headers = {
'Content-Type': 'application/json',
...headers
};
}
async get(endpoint, options = {}) {
return this._fetch(endpoint, {
method: 'GET',
...options
});
}
async post(endpoint, body, options = {}) {
return this._fetch(endpoint, {
method: 'POST',
body: JSON.stringify(body),
...options
});
}
async put(endpoint, body, options = {}) {
return this._fetch(endpoint, {
method: 'PUT',
body: JSON.stringify(body),
...options
});
}
async delete(endpoint, options = {}) {
return this._fetch(endpoint, {
method: 'DELETE',
...options
});
}
async _fetch(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const headers = { ...this.headers, ...options.headers };
const config = {
...options,
headers,
credentials: 'include' // 默认包含 cookies
};
const response = await fetch(url, config);
if (!response.ok) {
const error = new Error(`HTTP错误! 状态码: ${response.status}`);
error.status = response.status;
error.response = response;
throw error;
}
// 根据响应内容类型决定如何解析
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
return response.json();
}
return response.text();
}
}
// 使用示例
const api = new HttpClient('https://api.example.com', {
'Authorization': 'Bearer your_token_here'
});
// 获取用户
api.get('/users/1')
.then(user => console.log(user))
.catch(error => console.error(error));
// 创建新用户
api.post('/users', {
name: 'John Doe',
email: 'john@example.com'
}).then(result => console.log(result));
流式数据处理高级用法
Fetch API 的流式处理能力使其特别适合处理大文件或实时数据:
分块读取文本数据
fetch('https://api.example.com/large-text-file.txt')
.then(response => {
const reader = response.body.getReader();
const decoder = new TextDecoder();
let result = '';
function readChunk({ done, value }) {
if (done) {
console.log('完整内容:', result);
return;
}
result += decoder.decode(value, { stream: true });
return reader.read().then(readChunk);
}
return reader.read().then(readChunk);
});
处理大型 JSON 数据流
async function processLargeJsonStream(url) {
const response = await fetch(url);
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// 处理缓冲区中完整的JSON对象
let boundary;
while ((boundary = buffer.indexOf('}\n{')) !== -1) {
const completeJson = buffer.substring(0, boundary + 1);
buffer = buffer.substring(boundary + 2);
try {
const data = JSON.parse(completeJson);
console.log('处理数据:', data);
} catch (e) {
console.error('JSON解析错误:', e);
}
}
}
// 处理剩余数据
if (buffer) {
try {
const data = JSON.parse(buffer);
console.log('最后的数据:', data);
} catch (e) {
console.error('最后JSON解析错误:', e);
}
}
}
processLargeJsonStream('https://api.example.com/streaming-data');
总结
Fetch API 相比传统 Ajax 提供了以下优势:
- 简洁的语法:基于 Promise 的设计使代码更易读和维护
- 灵活的配置:通过配置对象轻松设置请求方法、头部、主体等
- 强大的功能:支持流式处理、请求中断、并行请求等高级特性
- 多种数据处理方式:支持 JSON、文本、二进制等多种响应格式
- 现代标准:是浏览器原生支持的现代 Web 标准
通过上述示例和封装,我们可以看到 Fetch API 如何简化网络请求的处理,使开发者能够更专注于业务逻辑而不是底层实现细节。特别是其流式处理能力,为处理大文件和实时数据提供了优雅的解决方案。