Web API网络-HTTP请求详解
# Web API网络-HTTP请求详解
现代Web应用离不开HTTP请求。本文将深入讲解Fetch API和XMLHttpRequest两种HTTP请求方式,涵盖请求配置、响应处理、错误处理、取消请求等实用技巧。
# 一、Fetch API基础
Fetch API是现代浏览器提供的原生HTTP请求接口,基于Promise,语法简洁。
# 1.1 基本用法
// 最简单的GET请求
fetch('https://api.example.com/data')
.then(response => 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');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('请求失败:', error);
}
}
# 1.2 Request对象
// 发送POST请求
fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
},
body: JSON.stringify({
name: '张三',
email: 'zhangsan@example.com'
})
})
.then(response => response.json())
.then(data => console.log(data));
// 完整配置示例
fetch('https://api.example.com/data', {
method: 'GET', // 请求方法
headers: {}, // 请求头
body: null, // 请求体(GET/HEAD不能有body)
mode: 'cors', // 请求模式:same-origin, no-cors, cors
credentials: 'include', // 凭证:omit, same-origin, include
cache: 'default', // 缓存模式:default, no-cache, reload, force-cache, only-if-cached
redirect: 'follow', // 重定向:follow, error, manual
referrer: 'client', // 引用页
referrerPolicy: 'no-referrer-when-downgrade', // 引用策略
integrity: '', // 子资源完整性值
keepalive: false, // 页面卸载后保持连接
signal: abortController.signal // 用于取消请求
});
# 1.3 Response对象
async function handleResponse() {
const response = await fetch('https://api.example.com/data');
// 响应状态
console.log(response.status); // 200, 404, 500 等
console.log(response.statusText); // "OK", "Not Found" 等
console.log(response.ok); // status 200-299 返回true
// 响应头
console.log(response.headers.get('Content-Type'));
console.log(response.headers.get('Date'));
// 遍历响应头
for (const [key, value] of response.headers) {
console.log(`${key}: ${value}`);
}
// 响应类型
console.log(response.type); // basic, cors, error, opaque
// 响应URL
console.log(response.url);
// 是否重定向
console.log(response.redirected);
}
# 1.4 解析响应体
// 解析JSON
const data = await response.json();
// 解析文本
const text = await response.text();
// 解析Blob(文件、图片等)
const blob = await response.blob();
// 解析ArrayBuffer(二进制数据)
const buffer = await response.arrayBuffer();
// 解析FormData
const formData = await response.formData();
// 克隆响应(只能读取一次,克隆后可多次读取)
const response1 = await fetch('/api/data');
const response2 = response1.clone();
const json = await response1.json();
const text = await response2.text();
# 二、Fetch高级用法
# 2.1 发送不同类型的数据
// 1. JSON数据
fetch('/api/user', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: '张三', age: 25 })
});
// 2. 表单数据(application/x-www-form-urlencoded)
const formData = new URLSearchParams();
formData.append('name', '张三');
formData.append('age', '25');
fetch('/api/user', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: formData
});
// 3. FormData(multipart/form-data,用于文件上传)
const formData2 = new FormData();
formData2.append('name', '张三');
formData2.append('file', fileInput.files[0]);
fetch('/api/upload', {
method: 'POST',
body: formData2 // 不需要设置Content-Type,浏览器自动设置
});
// 4. 文本数据
fetch('/api/note', {
method: 'POST',
headers: {
'Content-Type': 'text/plain'
},
body: '这是一段文本内容'
});
// 5. Blob数据
const blob = new Blob(['文件内容'], { type: 'text/plain' });
fetch('/api/file', {
method: 'POST',
body: blob
});
# 2.2 处理不同状态码
async function handleRequest() {
try {
const response = await fetch('/api/data');
// 检查响应状态
if (!response.ok) {
// HTTP错误状态
if (response.status === 404) {
throw new Error('资源不存在');
} else if (response.status === 401) {
throw new Error('未授权,请登录');
} else if (response.status === 500) {
throw new Error('服务器错误');
} else {
throw new Error(`HTTP错误: ${response.status}`);
}
}
const data = await response.json();
return data;
} catch (error) {
console.error('请求失败:', error.message);
throw error;
}
}
// 封装错误处理
async function fetchWithErrorHandling(url, options) {
const response = await fetch(url, options);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
const error = new Error(errorData.message || `HTTP ${response.status}`);
error.status = response.status;
error.data = errorData;
throw error;
}
return response;
}
# 2.3 取消请求
// 使用AbortController取消请求
const controller = new AbortController();
const signal = controller.signal;
fetch('/api/data', { signal })
.then(response => response.json())
.then(data => console.log(data))
.catch(error => {
if (error.name === 'AbortError') {
console.log('请求已取消');
} else {
console.error('请求失败:', error);
}
});
// 5秒后取消请求
setTimeout(() => {
controller.abort();
}, 5000);
// 实际应用:搜索防抖
let controller = null;
function search(keyword) {
// 取消上一次请求
if (controller) {
controller.abort();
}
controller = new AbortController();
fetch(`/api/search?q=${keyword}`, { signal: controller.signal })
.then(response => response.json())
.then(data => {
displayResults(data);
})
.catch(error => {
if (error.name !== 'AbortError') {
console.error('搜索失败:', error);
}
});
}
# 2.4 超时控制
// Fetch没有内置超时,需要手动实现
function fetchWithTimeout(url, options = {}, timeout = 5000) {
return Promise.race([
fetch(url, options),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('请求超时')), timeout)
)
]);
}
// 使用示例
try {
const response = await fetchWithTimeout('/api/data', {}, 3000);
const data = await response.json();
} catch (error) {
console.error(error.message); // "请求超时"
}
// 结合AbortController实现超时
function fetchWithAbortTimeout(url, options = {}, timeout = 5000) {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
return fetch(url, {
...options,
signal: controller.signal
}).finally(() => {
clearTimeout(id);
});
}
# 2.5 上传进度
// Fetch API本身不支持上传进度,需要使用XMLHttpRequest
// 或使用fetch结合ReadableStream
async function uploadWithProgress(file, onProgress) {
const formData = new FormData();
formData.append('file', file);
// 注意:Fetch不支持原生上传进度,需要服务端配合
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
return response.json();
}
// 下载进度
async function downloadWithProgress(url, onProgress) {
const response = await fetch(url);
const contentLength = response.headers.get('Content-Length');
const total = parseInt(contentLength, 10);
let loaded = 0;
const reader = response.body.getReader();
const chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
loaded += value.length;
if (onProgress) {
onProgress({ loaded, total, percentage: (loaded / total * 100).toFixed(2) });
}
}
const blob = new Blob(chunks);
return blob;
}
// 使用示例
downloadWithProgress('/api/file', (progress) => {
console.log(`下载进度: ${progress.percentage}%`);
});
# 三、XMLHttpRequest详解
XMLHttpRequest是传统的HTTP请求API,功能强大但语法复杂。
# 3.1 基本用法
const xhr = new XMLHttpRequest();
// 初始化请求
xhr.open('GET', 'https://api.example.com/data', true);
// 设置请求头
xhr.setRequestHeader('Content-Type', 'application/json');
// 设置响应类型
xhr.responseType = 'json';
// 监听状态变化
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
console.log(xhr.response);
} else {
console.error('请求失败:', xhr.status);
}
}
};
// 发送请求
xhr.send();
# 3.2 readyState状态
xhr.onreadystatechange = function() {
console.log('readyState:', xhr.readyState);
// 0: UNSENT - 代理被创建,但尚未调用open()
// 1: OPENED - open()已被调用
// 2: HEADERS_RECEIVED - send()已被调用,响应头已被接收
// 3: LOADING - 响应体接收中
// 4: DONE - 请求完成
};
# 3.3 完整示例
function ajax(options) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
// 默认配置
const config = {
method: 'GET',
url: '',
headers: {},
data: null,
timeout: 0,
withCredentials: false,
responseType: 'json',
...options
};
// 初始化
xhr.open(config.method, config.url, true);
// 设置响应类型
xhr.responseType = config.responseType;
// 设置超时
if (config.timeout) {
xhr.timeout = config.timeout;
}
// 跨域携带凭证
xhr.withCredentials = config.withCredentials;
// 设置请求头
Object.keys(config.headers).forEach(key => {
xhr.setRequestHeader(key, config.headers[key]);
});
// 成功回调
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
resolve({
data: xhr.response,
status: xhr.status,
statusText: xhr.statusText,
headers: xhr.getAllResponseHeaders()
});
} else {
reject(new Error(`HTTP ${xhr.status}: ${xhr.statusText}`));
}
};
// 错误回调
xhr.onerror = function() {
reject(new Error('网络错误'));
};
// 超时回调
xhr.ontimeout = function() {
reject(new Error('请求超时'));
};
// 取消回调
xhr.onabort = function() {
reject(new Error('请求已取消'));
};
// 发送请求
xhr.send(config.data);
});
}
// 使用示例
ajax({
method: 'POST',
url: '/api/user',
headers: {
'Content-Type': 'application/json'
},
data: JSON.stringify({ name: '张三' }),
timeout: 5000
})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error.message);
});
# 3.4 上传进度
function uploadFile(file, onProgress) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append('file', file);
// 监听上传进度
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
const percentage = (e.loaded / e.total * 100).toFixed(2);
if (onProgress) {
onProgress({
loaded: e.loaded,
total: e.total,
percentage: percentage
});
}
}
});
// 上传开始
xhr.upload.addEventListener('loadstart', () => {
console.log('上传开始');
});
// 上传完成
xhr.upload.addEventListener('load', () => {
console.log('上传完成');
});
// 上传错误
xhr.upload.addEventListener('error', () => {
reject(new Error('上传失败'));
});
// 上传中止
xhr.upload.addEventListener('abort', () => {
reject(new Error('上传已取消'));
});
xhr.onload = function() {
if (xhr.status === 200) {
resolve(JSON.parse(xhr.responseText));
} else {
reject(new Error(`上传失败: ${xhr.status}`));
}
};
xhr.open('POST', '/api/upload');
xhr.send(formData);
});
}
// 使用示例
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', async () => {
const file = fileInput.files[0];
try {
const result = await uploadFile(file, (progress) => {
console.log(`上传进度: ${progress.percentage}%`);
updateProgressBar(progress.percentage);
});
console.log('上传成功:', result);
} catch (error) {
console.error(error.message);
}
});
# 3.5 下载进度
function downloadFile(url, onProgress) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
// 监听下载进度
xhr.addEventListener('progress', (e) => {
if (e.lengthComputable) {
const percentage = (e.loaded / e.total * 100).toFixed(2);
if (onProgress) {
onProgress({
loaded: e.loaded,
total: e.total,
percentage: percentage
});
}
}
});
xhr.onload = function() {
if (xhr.status === 200) {
resolve(xhr.response);
} else {
reject(new Error(`下载失败: ${xhr.status}`));
}
};
xhr.onerror = function() {
reject(new Error('下载错误'));
};
xhr.open('GET', url);
xhr.responseType = 'blob';
xhr.send();
});
}
// 使用示例
downloadFile('/api/file/large.pdf', (progress) => {
console.log(`下载进度: ${progress.percentage}%`);
})
.then(blob => {
// 创建下载链接
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'file.pdf';
a.click();
URL.revokeObjectURL(url);
});
# 四、Fetch vs XMLHttpRequest
# 4.1 对比表格
| 特性 | Fetch | XMLHttpRequest |
|---|---|---|
| 语法 | 简洁(Promise) | 复杂(回调) |
| 上传进度 | 不支持 | 支持 |
| 下载进度 | 支持(复杂) | 支持 |
| 超时 | 不支持(需手动实现) | 支持 |
| 取消请求 | AbortController | abort() |
| 请求拦截 | 不支持 | 支持 |
| 响应拦截 | 不支持 | 支持 |
| 兼容性 | IE不支持 | 全部浏览器 |
# 4.2 选择建议
// 优先使用Fetch(现代浏览器)
// - 简单的GET/POST请求
// - 不需要进度监控
// - 基于Promise的现代代码
// 使用XMLHttpRequest
// - 需要上传/下载进度
// - 需要兼容老浏览器
// - 需要更精细的控制
# 五、综合示例
# 六、最佳实践
# 6.1 错误处理
async function fetchWithRetry(url, options = {}, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response;
} catch (error) {
if (i === retries - 1) {
throw error;
}
console.log(`重试第 ${i + 1} 次...`);
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
}
# 6.2 请求拦截
class HttpClient {
constructor(baseURL = '') {
this.baseURL = baseURL;
this.interceptors = {
request: [],
response: []
};
}
use(type, handler) {
this.interceptors[type].push(handler);
}
async fetch(url, options = {}) {
url = this.baseURL + url;
for (const interceptor of this.interceptors.request) {
const result = await interceptor({ url, options });
if (result) {
url = result.url;
options = result.options;
}
}
let response = await fetch(url, options);
for (const interceptor of this.interceptors.response) {
response = await interceptor(response);
}
return response;
}
}
const client = new HttpClient('https://api.example.com');
client.use('request', async ({ url, options }) => {
options.headers = {
...options.headers,
'Authorization': `Bearer ${getToken()}`
};
return { url, options };
});
client.use('response', async (response) => {
if (response.status === 401) {
await refreshToken();
throw new Error('需要重新登录');
}
return response;
});
# 6.3 并发控制
async function fetchWithConcurrency(urls, limit = 3) {
const results = [];
const executing = [];
for (const url of urls) {
const promise = fetch(url).then(r => r.json());
results.push(promise);
if (limit <= urls.length) {
const e = promise.then(() => executing.splice(executing.indexOf(e), 1));
executing.push(e);
if (executing.length >= limit) {
await Promise.race(executing);
}
}
}
return Promise.all(results);
}
# 七、总结
HTTP请求是Web应用的基础:
- Fetch API:现代、简洁、基于Promise
- XMLHttpRequest:功能完整、支持进度监控
- 选择建议:优先Fetch,需要进度时用XHR
- 最佳实践:错误处理、请求拦截、并发控制
掌握HTTP请求API,能够构建高效、可靠的数据交互应用。
祝你变得更强!
编辑 (opens new window)
上次更新: 2025/11/28