📚 JavaScript 网络请求
在现代 Web 开发中,网络请求是与服务器交互的核心手段。本文将从基础 API 到高级应用,系统讲解 JavaScript 网络请求的技术要点和最佳实践。
🌐 一、核心请求方法
1. XMLHttpRequest(传统方式)
javascript
const xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data");
xhr.onload = function () {
if (xhr.status >= 200 && xhr.status < 300) {
console.log(JSON.parse(xhr.responseText));
} else {
console.error("请求失败:", xhr.statusText);
}
};
xhr.onerror = function () {
console.error("网络连接异常");
};
xhr.send();2. Fetch API(现代方式)
javascript
// 基础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));
// POST请求示例
fetch("https://api.example.com/submit", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer your_token",
},
body: JSON.stringify({ key: "value" }),
});3. Axios(第三方库)
javascript
axios
.get("https://api.example.com/data")
.then((response) => console.log(response.data))
.catch((error) => console.error("请求失败:", error));
// 并发请求
Promise.all([axios.get("/api/users"), axios.get("/api/posts")]).then(
([usersRes, postsRes]) => {
console.log("用户数据:", usersRes.data);
console.log("文章数据:", postsRes.data);
}
);4. 文件上传(XMLHttpRequest 与 Fetch 的区别)
javascript
// XMLHttpRequest 文件上传(支持进度监控)
const xhrUpload = (file) => {
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append("file", file);
// 上传进度事件
xhr.upload.onprogress = (e) => {
const percent = Math.round((e.loaded / e.total) * 100);
console.log(`上传进度: ${percent}%`);
};
xhr.open("POST", "/upload");
xhr.send(formData);
};
// Fetch 文件上传(需手动处理请求体)
const fetchUpload = async (file) => {
const formData = new FormData();
formData.append("file", file);
try {
const response = await fetch("/upload", {
method: "POST",
body: formData, // 自动设置 Content-Type
});
return response.json();
} catch (error) {
console.error("上传失败:", error);
}
};🛡️ 二、请求控制
1. 中断请求
javascript
// Fetch中断
const controller = new AbortController();
fetch("/api/data", {
signal: controller.signal,
}).catch((err) => {
if (err.name === "AbortError") {
console.log("请求被主动取消");
}
});
// 5秒后取消请求
setTimeout(() => controller.abort(), 5000);
// Axios中断
const source = axios.CancelToken.source();
axios.get("/api/data", {
cancelToken: source.token,
});
source.cancel("用户取消操作");🌉 三、跨域处理
1. CORS 配置
javascript
// 服务端响应头示例
Access-Control-Allow-Origin: https://your-domain.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type, Authorization2. 代理方案
javascript
// 开发环境代理配置(vite.config.js)
export default defineConfig({
server: {
proxy: {
"/api": {
target: "http://backend-service:3000",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ""),
},
},
},
});🔐 四、安全认证
1. Cookie 认证
javascript
// 携带凭据
fetch("/api/auth", {
credentials: "include", // 等价于axios的withCredentials: true
});
// 设置Cookie属性
document.cookie = `sessionId=abc123; Path=/; Secure; SameSite=Strict`;2. JWT 认证
javascript
// 请求头携带Token
axios.interceptors.request.use((config) => {
const token = localStorage.getItem("jwt");
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});🚀 五、性能优化
1. 缓存策略
javascript
// 强制缓存验证
fetch("/api/data", {
headers: {
"Cache-Control": "max-age=300", // 缓存5分钟
},
});
// 版本化请求
axios.get("/api/data?v=20230815");2. 数据压缩
javascript
// 服务端启用gzip
// nginx配置示例
gzip on;
gzip_types text/plain application/json;📊 六、监控调试
1. 性能指标
javascript
const { duration, decodedBodySize } = performance.getEntriesByName(url)[0];
console.log(`下载${decodedBodySize}字节,耗时${duration}ms`);
// 超时警告
if (duration > 3000) {
console.warn("请求响应时间过长");
}💡 最佳实践
- 超时兜底:所有请求必须设置超时时间
- 错误重试:网络错误时自动重试(最多3次)
- 取消重复:相同请求未完成时阻止重复发送
- 数据校验:验证响应数据格式
- 安全审计:定期检查请求头安全配置
点击查看实例代码
javascript
// 超时控制(5秒超时)
const fetchWithTimeout = (url, options = {}, timeout = 5000) => {
const controller = new AbortController();
options.signal = controller.signal;
const timeoutId = setTimeout(() => {
controller.abort();
throw new Error(`请求超时:${timeout}ms`);
}, timeout);
return fetch(url, options).finally(() => clearTimeout(timeoutId));
};
// 请求错误重试
const fetchWithRetry = (url, retries = 3) => {
return fetch(url).catch((err) =>
retries > 1 ? fetchWithRetry(url, retries - 1) : Promise.reject(err)
);
};
// 请求去重缓存
const pendingRequests = new Map();
const fetchDeduplicated = async (url, options) => {
const requestKey = `${url}_${JSON.stringify(options)}`;
if (pendingRequests.has(requestKey)) {
return pendingRequests.get(requestKey);
}
const promise = fetch(url, options).finally(() =>
pendingRequests.delete(requestKey)
);
pendingRequests.set(requestKey, promise);
return promise;
};
// 数据格式校验
const validateSchema = (schema) => (data) => {
const ajv = new Ajv();
const validate = ajv.compile(schema);
if (!validate(data)) {
throw new Error(`数据校验失败: ${JSON.stringify(validate.errors)}`);
}
return data;
};
// 用户信息schema
const userSchema = {
type: "object",
properties: {
id: { type: "number" },
name: { type: "string" },
},
required: ["id", "name"],
};
// 带校验的请求示例
fetch("/api/user/123")
.then((response) => response.json())
.then(validateSchema(userSchema))
.then((userData) => console.log("有效用户数据:", userData))
.catch((error) => console.error("请求失败:", error));