AJAX与fetch API有何不同 从原理到实战应用全面解析 帮你解决异步请求选择困难

引言:异步请求的演进与选择困境
在现代Web开发中,异步请求是实现动态网页的核心技术。从早期的XMLHttpRequest(AJAX)到现代的Fetch API,技术不断演进,但这也给开发者带来了选择困难。本文将从原理、使用方式、功能特性、浏览器兼容性、错误处理、性能优化等多个维度,全面解析AJAX与Fetch API的区别,并通过实战案例帮助你做出最佳选择。
一、AJAX(XMLHttpRequest)详解
1.1 AJAX的基本概念
AJAX(Asynchronous JavaScript and XML)是一种创建交互式网页应用的网页开发技术。虽然名字中包含XML,但现代AJAX主要使用JSON格式。核心是通过XMLHttpRequest对象与服务器进行异步通信。
1.2 XMLHttpRequest对象的工作原理
XMLHttpRequest是一个浏览器API,允许JavaScript在不刷新页面的情况下发送HTTP请求和接收响应。它的工作流程包括:
创建XHR对象
配置请求(方法、URL、异步/同步)
设置请求头(可选)
发送请求
监听状态变化处理响应
1.3 AJAX的完整使用示例
下面是一个完整的AJAX请求示例,包含错误处理、超时设置和进度监控:
// 创建XMLHttpRequest对象
const xhr = new XMLHttpRequest();

// 配置请求:GET方法,异步请求,URL为API端点
xhr.open(‘GET’, ‘https://api.example.com/users’, true);

// 设置请求头(如果需要)
xhr.setRequestHeader(‘Content-Type’, ‘application/json’);
xhr.setRequestHeader(‘Authorization’, ‘Bearer token123’);

// 设置超时时间(毫秒)
xhr.timeout = 5000; // 5秒超时

// 监听超时事件
xhr.ontimeout = function() {
console.error(‘请求超时’);
showError(‘请求超时,请重试’);
};

// 监听进度事件(对于大文件上传/下载很有用)
xhr.onprogress = function(event) {
if (event.lengthComputable) {
const percentComplete = (event.loaded / event.total) * 100;
console.log(`下载进度: ${percentComplete.toFixed(2)}%`);
updateProgressBar(percentComplete);
}
};

// 监听状态变化
xhr.onreadystatechange = function() {
// readyState: 0-未初始化 1-已打开 2-已发送 3-接收中 4-完成
if (xhr.readyState === 4) {
// HTTP状态码
if (xhr.status >= 200 && xhr.status < 300) { try { // 解析JSON响应 const data = JSON.parse(xhr.responseText); console.log('请求成功:', data); displayUsers(data); } catch (e) { console.error('JSON解析失败:', e); showError('响应格式错误'); } } else if (xhr.status === 401) { console.error('未授权'); redirectToLogin(); } else if (xhr.status === 404) { console.error('资源未找到'); showError('请求的资源不存在'); } else if (xhr.status >= 500) {
console.error(‘服务器错误’);
showError(‘服务器错误,请稍后重试’);
} else {
console.error(‘请求失败:’, xhr.status, xhr.statusText);
showError(‘请求失败,请检查网络连接’);
}
}
};

// 监听错误事件
xhr.onerror = function() {
console.error(‘网络错误’);
showError(‘网络连接失败,请检查网络’);
};

// 发送请求(对于GET请求,body为null)
xhr.send(null);

// 取消请求(如果需要)
// xhr.abort();
1.4 AJAX的优缺点分析
优点:
浏览器兼容性极好(IE7+)
功能全面,支持上传/下载进度监控
支持同步和异步请求
支持取消请求
支持设置超时
支持监听各种事件(progress、timeout、readystatechange等)
缺点:
API设计复杂,基于事件监听,不够直观
需要手动解析响应数据
链式调用困难,容易形成回调地狱
需要手动处理请求头和错误状态码
代码冗长,可读性差
二、Fetch API详解
2.1 Fetch API的基本概念
Fetch API是现代浏览器提供的原生API,用于发起网络请求。它基于Promise设计,提供了更简洁、更强大的接口。Fetch是XMLHttpRequest的下一代替代方案,但不是完全替代。
2.2 Fetch的工作原理
Fetch API的工作流程:
调用fetch()函数返回Promise
Promise解析为Response对象
使用Response对象的方法解析数据
处理错误
2.3 Fetch的完整使用示例
// 基本GET请求
fetch(‘https://api.example.com/users’)
.then(response => {
// 检查HTTP状态码
if (!response.ok) {
// response.ok为true表示200-299状态码
throw new Error(`HTTP error! status: ${response.status}`);
}
// 解析JSON数据
return response.json();
})
.then(data => {
console.log(‘请求成功:’, data);
displayUsers(data);
})
.catch(error => {
console.error(‘请求失败:’, error);
showError(error.message);
});

// POST请求示例
const userData = {
name: ‘John Doe’,
email: ‘john@example.com’
};

fetch(‘https://api.example.com/users’, {
method: ‘POST’,
headers: {
‘Content-Type’: ‘application/json’,
‘Authorization’: ‘Bearer token123’
},
body: JSON.stringify(userData)
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
console.log(‘用户创建成功:’, data);
showSuccess(‘用户创建成功’);
})
.catch(error => {
console.error(‘创建失败:’, error);
showError(‘创建失败: ‘ + error.message);
});

// 使用async/await的现代写法
async function fetchUsers() {
try {
const response = await fetch(‘https://api.example.com/users’, {
method: ‘GET’,
headers: {
‘Authorization’: ‘Bearer token123’
}
});

if (!response.ok) {
// 处理不同状态码
if (response.status === 401) {
throw new Error(‘未授权,请重新登录’);
} else if (response.status === 404) {
throw new Error(‘用户不存在’);
} else if (response.status >= 500) {
throw new Error(‘服务器错误,请稍后重试’);
} else {
throw new Error(`请求失败: ${response.status}`);
}
}

const data = await response.json();
console.log(‘用户数据:’, data);
return data;
} catch (error) {
console.error(‘获取用户失败:’, error);
showError(error.message);
throw error;
}
}

// 带超时控制的Fetch请求
function fetchWithTimeout(url, options = {}, timeout = 5000) {
return Promise.race([
fetch(url, options),
new Promise((_, reject) =>
setTimeout(() => reject(new Error(‘请求超时’)), timeout)
)
]);
}

// 使用带超时的Fetch
fetchWithTimeout(‘https://api.example.com/users’)
.then(response => response.json())
.then(data => console.log(data))
.catch(error => {
if (error.message === ‘请求超时’) {
showError(‘请求超时,请重试’);
} else {
showError(error.message);
}
});
2.4 Fetch的优缺点分析
优点:
基于Promise,支持链式调用和async/await
API设计简洁直观
代码可读性好,逻辑清晰
原生支持JSON数据处理
与现代JavaScript特性完美结合
缺点:
不支持上传/下载进度监控(需要XMLHttpRequest)
默认不携带cookie(需要设置credentials: ‘include’)
错误处理需要手动检查response.ok
超时控制需要额外封装
浏览器兼容性相对较新(IE不支持)
无法取消请求(需要AbortController)
三、AJAX与Fetch的深度对比
3.1 API设计与使用方式对比
特性
AJAX (XMLHttpRequest)
Fetch API
语法风格
基于事件和回调
基于Promise
代码长度
冗长,需要多行代码
简洁,链式调用
可读性
较差,容易形成回调地狱
优秀,逻辑清晰
数据处理
需要手动解析(JSON.parse)
内置response.json()方法
链式调用
困难
天然支持
3.2 功能特性对比
功能
AJAX
Fetch
说明
进度监控
✅ 支持onprogress
❌ 不支持
大文件传输时AJAX更优
超时控制
✅ timeout属性
❌ 需要封装
AJAX原生支持
取消请求
✅ abort()方法
❌ 需要AbortController
Fetch需要额外API
同步请求
✅ 支持
❌ 仅异步
Fetch仅支持异步
请求头设置
✅ setRequestHeader
✅ headers对象
两者都支持
Cookie控制
✅ withCredentials
✅ credentials选项
两者都支持,但默认行为不同
3.3 错误处理对比
AJAX错误处理:
xhr.onerror = function() {
// 网络错误
console.error(‘网络错误’);
};

xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) { // 成功 } else { // HTTP错误 console.error('HTTP错误:', xhr.status); } } }; Fetch错误处理: fetch(url) .then(response => {
if (!response.ok) {
throw new Error(`HTTP错误: ${response.status}`);
}
return response.json();
})
.catch(error => {
// 网络错误或HTTP错误
console.error(‘请求失败:’, error);
});
3.4 浏览器兼容性对比
浏览器
AJAX (XMLHttpRequest)
Fetch API
IE
✅ IE7+
❌ 不支持
Edge
✅
✅ 14+
Chrome
✅
✅ 42+
Firefox
✅
✅ 39+
Safari
✅
✅ 10.1+
移动端
✅
✅ 现代浏览器
兼容性建议:
如果需要支持IE,必须使用AJAX
现代项目(2023年)可以优先使用Fetch
可以使用polyfill(如whatwg-fetch)来兼容旧浏览器
3.5 性能与资源消耗对比
指标
AJAX
Fetch
说明
内存占用
较高
较低
Fetch基于Promise,资源管理更优
代码体积
较大
较小
Fetch代码更简洁
执行效率
相当
相当
底层都是浏览器网络栈
可维护性
较差
优秀
Fetch代码更易维护
四、实战应用场景分析
4.1 场景一:简单API调用(推荐Fetch)
需求:
从后端获取用户列表并显示
AJAX实现:
function getUsersAJAX() {
const xhr = new XMLHttpRequest();
xhr.open(‘GET’, ‘/api/users’, true);
xhr.onload = function() {
if (xhr.status === 200) {
const users = JSON.parse(xhr.responseText);
displayUsers(users);
} else {
showError(‘获取用户失败’);
}
};
xhr.onerror = function() {
showError(‘网络错误’);
};
xhr.send();
}
Fetch实现:
async function getUsersFetch() {
try {
const response = await fetch(‘/api/users’);
if (!response.ok) throw new Error(‘获取用户失败’);
const users = await response.json();
displayUsers(users);
} catch (error) {
showError(error.message);
}
}
推荐:
使用Fetch,代码更简洁,逻辑更清晰。
4.2 场景二:大文件上传/下载(推荐AJAX)
需求:
上传大文件并显示进度条
AJAX实现:
function uploadFileWithProgress(file) {
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append(‘file’, file);

xhr.upload.onprogress = function(e) {
if (e.lengthComputable) {
const percent = (e.loaded / e.total) * 100;
updateProgress(percent);
}
};

xhr.onload = function() {
if (xhr.status === 200) {
showSuccess(‘上传成功’);
} else {
showError(‘上传失败’);
}
};

xhr.open(‘POST’, ‘/api/upload’);
xhr.send(formData);
}
Fetch实现:
Fetch无法直接监控进度,需要使用XMLHttpRequest或fetch的底层替代方案。
推荐:
使用AJAX,因为需要进度监控。
4.3 场景三:需要取消请求(推荐AJAX或AbortController)
需求:
用户快速切换页面时取消之前的请求
AJAX实现:
let currentRequest = null;

function switchPage(page) {
// 取消之前的请求
if (currentRequest) {
currentRequest.abort();
}

const xhr = new XMLHttpRequest();
currentRequest = xhr;

xhr.open(‘GET’, `/api/data?page=${page}`, true);
xhr.onload = function() {
if (xhr.status === 200) {
displayData(JSON.parse(xhr.responseText));
}
};
xhr.send();
}
Fetch实现(使用AbortController):
let currentController = null;

async function switchPage(page) {
// 取消之前的请求
if (currentController) {
currentController.abort();
}

currentController = new AbortController();
const signal = currentController.signal;

try {
const response = await fetch(`/api/data?page=${page}`, { signal });
if (!response.ok) throw new Error(‘请求失败’);
const data = await response.json();
displayData(data);
} catch (error) {
if (error.name === ‘AbortError’) {
console.log(‘请求被取消’);
} else {
showError(error.message);
}
}
}
推荐:
两者都可以,但Fetch需要AbortController,代码稍复杂。
4.4 场景四:需要兼容IE(必须使用AJAX)
需求:
企业内部系统,需要支持IE11
AJAX实现:
如前所述的标准AJAX代码
Fetch实现:
不可行,除非使用polyfill
推荐:
必须使用AJAX
4.5 场景五:现代SPA应用(推荐Fetch)
需求:
React/Vue应用中的API调用
Fetch实现:
// 封装Fetch工具函数
const api = {
async get(url, params = {}) {
const query = new URLSearchParams(params).toString();
const fullUrl = query ? `${url}?${query}` : url;

const response = await fetch(fullUrl, {
headers: {
‘Authorization’: `Bearer ${getToken()}`
}
});

if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new Error(error.message || `HTTP ${response.status}`);
}

return response.json();
},

async post(url, data) {
const response = await fetch(url, {
method: ‘POST’,
headers: {
‘Content-Type’: ‘application/json’,
‘Authorization’: `Bearer ${getToken()}`
},
body: JSON.stringify(data)
});

if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new Error(error.message || `HTTP ${response.status}`);
}

return response.json();
}
};

// 使用示例
async function loadUserProfile(userId) {
try {
const user = await api.get(`/api/users/${userId}`);
return user;
} catch (error) {
console.error(‘加载用户失败:’, error);
throw error;
}
}
推荐:
使用Fetch,代码现代化,易于维护。
五、高级主题与最佳实践
5.1 请求取消的实现对比
AJAX取消:
const xhr = new XMLHttpRequest();
xhr.open(‘GET’, ‘/api/data’);
xhr.send();

// 取消请求
xhr.abort();
Fetch取消(AbortController):
const controller = new AbortController();

fetch(‘/api/data’, { signal: controller.signal })
.catch(error => {
if (error.name === ‘AbortError’) {
console.log(‘请求被取消’);
}
});

// 取消请求
controller.abort();
5.2 超时控制的实现
AJAX超时:
const xhr = new XMLHttpRequest();
xhr.timeout = 5000; // 5秒
xhr.ontimeout = function() {
console.error(‘请求超时’);
};
Fetch超时(需要封装):
function fetchWithTimeout(url, options = {}, timeout = 5000) {
return Promise.race([
fetch(url, options),
new Promise((_, reject) =>
setTimeout(() => reject(new Error(‘请求超时’)), timeout)
)
]);
}
5.3 并发请求控制
AJAX并发:
function sendMultipleRequests() {
const requests = [];
for (let i = 0; i < 5; i++) { const xhr = new XMLHttpRequest(); xhr.open('GET', `/api/data/${i}`, true); xhr.onload = function() { // 处理响应 }; xhr.send(); requests.push(xhr); } // 全部完成后处理 Promise.all(requests.map(xhr =>
new Promise((resolve, reject) => {
xhr.onload = () => resolve(xhr.responseText);
xhr.onerror = reject;
})
)).then(results => {
console.log(‘所有请求完成:’, results);
});
}
Fetch并发:
async function sendMultipleRequests() {
const urls = [‘/api/data/0’, ‘/api/data/1’, ‘/api/data/2’];

try {
const responses = await Promise.all(
urls.map(url => fetch(url))
);

// 检查所有响应状态
const allOk = responses.every(r => r.ok);
if (!allOk) {
throw new Error(‘部分请求失败’);
}

const data = await Promise.all(
responses.map(r => r.json())
);

console.log(‘所有请求完成:’, data);
} catch (error) {
console.error(‘请求失败:’, error);
}
}
5.4 请求重试机制
AJAX重试:
function fetchWithRetry(url, options = {}, maxRetries = 3) {
return new Promise((resolve, reject) => {
const attempt = (retryCount) => {
const xhr = new XMLHttpRequest();
xhr.open(options.method || ‘GET’, url, true);

xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) { resolve(xhr.responseText); } else if (retryCount < maxRetries) { console.log(`重试 ${retryCount + 1}/${maxRetries}`); setTimeout(() => attempt(retryCount + 1), 1000);
} else {
reject(new Error(`HTTP ${xhr.status}`));
}
};

xhr.onerror = function() {
if (retryCount < maxRetries) { console.log(`重试 ${retryCount + 1}/${maxRetries}`); setTimeout(() => attempt(retryCount + 1), 1000);
} else {
reject(new Error(‘网络错误’));
}
};

xhr.send(options.body || null);
};

attempt(0);
});
}
Fetch重试:
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
for (let i = 0; i <= maxRetries; i++) { try { const response = await fetch(url, options); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } return await response.json(); } catch (error) { if (i === maxRetries) { throw error; } console.log(`重试 ${i + 1}/${maxRetries}`); await new Promise(resolve => setTimeout(resolve, 1000));
}
}
}
5.5 请求缓存策略
AJAX缓存控制:
// 通过URL参数防止缓存
const timestamp = new Date().getTime();
xhr.open(‘GET’, `/api/data?t=${timestamp}`, true);
Fetch缓存控制:
// Fetch的cache选项
fetch(‘/api/data’, {
cache: ‘no-cache’ // 或 ‘default’, ‘no-store’, ‘reload’, ‘force-cache’, ‘only-if-cached’
});
5.6 跨域请求处理
AJAX跨域:
// 需要服务器设置CORS头
const xhr = new XMLHttpRequest();
xhr.open(‘GET’, ‘https://api.otherdomain.com/data’, true);
xhr.withCredentials = true; // 携带凭证
xhr.send();
Fetch跨域:
fetch(‘https://api.otherdomain.com/data’, {
credentials: ‘include’, // 携带凭证
mode: ‘cors’ // 跨域模式
});
六、现代项目中的选择策略
6.1 选择决策树
需要支持IE?
├── 是 → 使用AJAX
└── 否 → 继续判断
需要进度监控?
├── 是 → 使用AJAX
└── 否 → 继续判断
需要取消请求?
├── 是 → 使用Fetch + AbortController
└── 否 → 使用Fetch
6.2 混合使用策略
在某些情况下,可以混合使用两者:
// 封装统一的请求接口
function request(url, options = {}) {
// 如果需要进度监控,使用AJAX
if (options.onProgress) {
return ajaxWithProgress(url, options);
}

// 如果需要取消,使用Fetch + AbortController
if (options.signal) {
return fetch(url, options);
}

// 默认使用Fetch
return fetch(url, options).then(response => {
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
});
}

// 使用示例
request(‘/api/data’, {
onProgress: (percent) => console.log(percent)
});

request(‘/api/data’, {
signal: controller.signal
});
6.3 未来趋势
Fetch是未来
:现代浏览器和框架都在向Fetch迁移
AJAX不会消失
:在需要兼容旧浏览器或特殊功能时仍会使用
新API出现
:如WebTransport等新一代网络API正在发展中
框架封装
:Axios等库封装了底层细节,提供统一体验
七、总结与建议
7.1 核心差异总结
维度
AJAX
Fetch
设计理念
事件驱动
Promise驱动
使用复杂度
高
低
功能完整性
高(进度、取消、超时)
中(需额外API)
浏览器兼容性
极好
现代浏览器
代码可维护性
低
高
现代性
传统
现代
7.2 选择建议
使用AJAX的场景:
需要支持IE浏览器
需要上传/下载进度监控
需要同步请求(虽然不推荐)
需要直接控制请求生命周期
使用Fetch的场景:
现代Web应用(React/Vue/Angular)
需要简洁的代码和良好的可读性
使用async/await语法
不需要进度监控
可以接受使用AbortController处理取消
使用Axios等库的场景:
需要统一封装
需要拦截器、转换器等高级功能
需要同时支持浏览器和Node.js
不想处理底层差异
7.3 最终建议
新项目优先使用Fetch
:现代、简洁、易维护
老项目逐步迁移
:从AJAX向Fetch迁移
特殊需求灵活选择
:根据具体需求选择合适方案
考虑使用库
:Axios可以屏蔽底层差异,提供统一体验
无论选择哪种方案,理解其原理和适用场景才是最重要的。希望本文能帮助你解决异步请求的选择困难,在实际开发中做出最佳决策。