1. XMLHttpRequest (XHR)
XMLHttpRequest 是最早的异步网络请求 API,为现代 Web 应用奠定了基础。它提供了丰富的功能和细粒度的控制。
主要特点
- 支持同步和异步请求
- 可以设置请求头和获取响应头
- 支持各种数据类型(文本、JSON、XML等)
- 可以监控上传和下载进度
- 能够中止进行中的请求
基本用法
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);
xhr.onload = function () {
if (xhr.status === 200) {
console.log(xhr.responseText);
} else {
console.error('Request failed. Status:', xhr.status);
}
};
xhr.onerror = function () {
console.error('Network error occurred');
};
xhr.send();
同步请求(不推荐)
XHR 支持同步请求,但这种用法已被废弃,因为它会阻塞主线程:
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', false); // 同步请求
xhr.send();
if (xhr.status === 200) {
console.log(xhr.responseText);
}
取消请求
XHR 提供了一个简单直接的方法来取消进行中的请求:xhr.abort()
let xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);
xhr.send();
// 稍后取消请求
setTimeout(() => {
xhr.abort();
console.log('请求已取消');
}, 1000);
跨域处理
withCredentials这是 XHR 对象的一个布尔属性,默认值为 false,用于指示是否应该使用 credentials(如 cookies、HTTP 认证及客户端 SSL 证书)进行跨站点访问控制(CORS)请求。
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open('GET', 'https://api.example.com/data', true);
xhr.send();
-
当设置为
true时,浏览器会在跨域请求中包含 credentials, 它允许跨域请求携带敏感信息(如 cookies)。 -
只有在服务器端返回了
Access-Control-Allow-Credentials: true头部时,浏览器才会将响应暴露给前端 JavaScript 代码。 -
当使用
withCredentials时,Access-Control-Allow-Origin不能设置为*,必须指定具体的域名。
上传、下载进度
XHR 使用 progress 事件来跟踪下载进度,使用 upload.onprogress 事件来跟踪上传进度。
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://example.com/large-file');
// 下载进度
xhr.onprogress = function (event) {
if (event.lengthComputable) {
var percentComplete = (event.loaded / event.total) * 100;
console.log('Download progress: ' + percentComplete.toFixed(2) + '%');
}
};
xhr.onload = function () {
console.log('Download completed');
};
xhr.send();
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://example.com/upload');
// 上传进度
xhr.upload.onprogress = function (event) {
if (event.lengthComputable) {
var percentComplete = (event.loaded / event.total) * 100;
console.log('Upload progress: ' + percentComplete.toFixed(2) + '%');
}
};
xhr.onload = function () {
console.log('Upload completed');
};
var formData = new FormData();
formData.append('file', fileInput.files[0]);
xhr.send(formData);
2. Fetch API
Fetch API 是 XHR 的现代替代品,提供了更简洁、更强大的 API。它基于 Promise 设计,使异步操作更易于管理。
主要特点
- 基于 Promise,支持链式调用和 async/await
- 简洁的 API 设计
- 支持请求和响应流
- 内置 CORS 支持
- 更直观的请求配置
基本用法
fetch('https://api.example.com/data')
.then((response) => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then((data) => console.log(data))
.catch((error) => console.error('Fetch error:', error));
取消请求
Fetch API 使用 AbortController 接口来取消请求。这个方法稍微复杂一些,但提供了更多的灵活性。
// 创建一个 AbortController 实例
const controller = new AbortController();
fetch('https://api.example.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ key: 'value' }),
signal: controller.signal, // 这里把abort signal传入
})
.then((response) => response.json())
.then((data) => console.log(data));
// 1s后调用 controller.abort() 来取消请求
setTimeout(() => controller.abort(), 1000);
跨域处理
Fetch 默认遵循同源策略,但可以通过设置 mode 和 credentials 选项来控制跨域行为:
fetch('https://api.example.com/data', {
mode: 'cors',
credentials: 'include',
})
.then((response) => response.json())
.then((data) => console.log(data));
credentials这里不再过多说明,请查看文档,而mode 选项则用于指定请求的模式,其中 cors 和 no-cors 是两种常用的模式:
1. cors 模式
cors 是 Fetch 的默认模式,全称为"跨源资源共享"(Cross-Origin Resource Sharing),特点:
- 允许发送跨源请求
- 遵循 CORS 协议
- 可以访问响应的完整内容
- 需要服务器端的支持和配置
浏览器在发送实际请求前,可能会先发送一个预检请求(OPTIONS 方法),服务器需要设置适当的 CORS 头部来允许跨源访问,如果服务器配置正确,浏览器允许请求并可以访问响应。
2. no-cors 模式
no-cors 模式用于发送"简单"跨源请求,不触发 CORS 预检机制,特点:
- 可以发送跨源请求,但有严格限制
- 不需要服务器端的 CORS 支持
- 响应类型被限制为 "opaque",无法读取响应内容
- 主要用于缓存和某些资源(如图片)的加载
具体限制为:
- 只允许使用 GET、HEAD 或 POST 方法
- 不能设置自定义请求头
- 如果是 POST 请求,只能发送特定的 Content-Type(如 application/x-www-form-urlencoded、multipart/form-data 或 text/plain)
假设我们有一个场景,需要从第三方服务加载图片并缓存:
// 使用 no-cors 模式加载和缓存图片
fetch('https://third-party.com/image.jpg', {
mode: 'no-cors',
})
.then((response) => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return caches.open('image-cache').then((cache) => {
return cache.put('https://third-party.com/image.jpg', response);
});
})
.then(() => console.log('Image cached successfully'))
.catch((error) => console.error('Caching failed:', error));
在这个例子中,我们使用 no-cors 模式来加载可能不支持 CORS 的第三方图片。虽然我们无法直接操作响应内容,但可以将其缓存起来供后续使用。
cors 和 no-cors 的主要区别整理:
- 访问响应内容:
cors:可以完全访问响应内容。no-cors:无法访问响应内容,响应类型为 "opaque"。- 服务器配置要求:
cors:需要服务器配置正确的 CORS 头部。no-cors:不需要特殊的服务器配置。- 请求方法和头部限制:
cors:支持所有 HTTP 方法,可以设置自定义头部。no-cors:仅限于 GET、HEAD 和 POST 方法,不能设置自定义头部。- 用途:
cors:用于需要读取响应内容的跨源 API 请求。no-cors:主要用于加载跨源资源(如图片、脚本),或者向不支持 CORS 的 API 发送数据。- 安全性:
cors:提供更高的安全性,因为它需要服务器明确允许跨源访问。no-cors:安全性较低,因为它允许发送跨源请求而不需要服务器的明确许可。- 错误处理:
cors:可以捕获和处理详细的错误信息。no-cors:错误处理能力有限,因为无法访问响应详情。
上传、下载进度
Fetch API 本身不提供直接的进度事件,但可以通过 Response 对象的 body 属性(它是一个 ReadableStream)来实现类似的功能。
对于下载进度,可以使用 Response.body 和 ReadableStream API:
fetch('https://example.com/large-file')
.then((response) => {
const reader = response.body.getReader();
const contentLength = +response.headers.get('Content-Length');
let receivedLength = 0;
return new ReadableStream({
start(controller) {
function push() {
reader.read().then(({ done, value }) => {
if (done) {
controller.close();
return;
}
receivedLength += value.length;
const percentComplete = (receivedLength / contentLength) * 100;
console.log('Download progress: ' + percentComplete.toFixed(2) + '%');
controller.enqueue(value);
push();
});
}
push();
},
});
})
.then((stream) => new Response(stream))
.then((response) => response.blob())
.then((blob) => {
console.log('Download completed');
})
.catch((error) => console.error('Error:', error));
对于上传进度,Fetch API 没有内置的方法,只能通过Blob Stream和ReadableStream模拟进度:
function uploadWithProgress(url, file) {
const fileSize = file.size;
let uploadedSize = 0;
const stream = new ReadableStream({
start(controller) {
const reader = file.stream().getReader();
function push() {
reader.read().then(({ done, value }) => {
if (done) {
controller.close();
return;
}
uploadedSize += value.byteLength;
const progress = (uploadedSize / fileSize) * 100;
console.log(`Upload progress: ${progress.toFixed(2)}%`);
controller.enqueue(value);
push();
});
}
push();
},
});
return fetch(url, {
method: 'POST',
body: stream,
});
}
// 使用示例
const file = new File(['Hello, World!'], 'hello.txt', { type: 'text/plain' });
uploadWithProgress('https://example.com/upload', file)
.then((response) => response.json())
.then((data) => console.log('Upload complete:', data))
.catch((error) => console.error('Upload failed:', error));
3. sendBeacon
sendBeacon 方法用于在页面卸载时发送少量数据,特别适合发送分析或日志数据, 可以在用户埋点时使用。
主要特点
- 异步发送数据,不阻塞页面卸载
- 数据发送优先级较高,提高了到达率
- 不需要等待服务器响应
- 可靠地发送少量数据
基本用法
window.addEventListener('unload', function () {
navigator.sendBeacon(
'https://analytics.example.com/log',
JSON.stringify({
event: 'page_exit',
timestamp: new Date().getTime(),
}),
);
});
跨域处理
sendBeacon 不受同源策略限制,可以向任何域发送数据,这使得它特别适合发送分析或日志数据到第三方服务。
更多扩展
结合 Blob 使用以发送更复杂的数据:
class AnalyticsTracker {
constructor(url) {
this.url = url;
this.data = [];
}
trackEvent(category, action, label) {
this.data.push({ category, action, label, timestamp: new Date().toISOString() });
}
sendData() {
const blob = new Blob([JSON.stringify(this.data)], { type: 'application/json' });
navigator.sendBeacon(this.url, blob);
this.data = []; // 清空数据
}
}
const tracker = new AnalyticsTracker('https://analytics.example.com/log');
// 使用示例
tracker.trackEvent('button', 'click', 'submit-button');
window.addEventListener('unload', () => tracker.sendData());
tip:这个AnalyticsTracker只是个简单的示例,在实际应用中,AnalyticsTracker 可能会更复杂,包括更多功能,比如用户的跟踪、与特定分析平台(如 Google Analytics)的集成等。
SendBeacon的局限性
- 只支持 POST 请求
- 无法设置自定义请求头
- 不能接收响应数据
- 数据大小通常限制在 64KB 左右
- 不提供错误处理机制