Search K
Appearance
Appearance
📊 SEO元描述:2024年最新Fetch高级功能教程,深入讲解文件上传、Stream流处理、AbortController请求取消。包含完整代码示例,适合前端开发者掌握现代网络编程高级技术。
核心关键词:Fetch高级功能2024、JavaScript文件上传、Stream流处理、AbortController取消请求、前端网络编程
长尾关键词:Fetch怎么上传文件、JavaScript流处理教程、请求取消怎么实现、前端大文件上传、现代网络编程技术
通过本节Fetch高级功能详解,你将系统性掌握:
Fetch高级功能是什么?这是现代Web应用处理复杂数据交互的关键技术。Fetch的高级功能包括文件上传、流处理、请求取消等特性,它们解决了传统网络编程中的性能瓶颈和用户体验问题,也是企业级Web应用的技术基础。
💡 学习建议:掌握Fetch高级功能是构建现代Web应用的必备技能,特别是在处理大数据和复杂交互场景中。
文件上传是Web应用中最常见的需求之一,Fetch提供了强大的文件处理能力:
// 🎉 完整的文件上传解决方案
class FileUploader {
constructor() {
this.uploadQueue = [];
this.maxConcurrent = 3;
this.chunkSize = 1024 * 1024; // 1MB chunks
this.activeUploads = new Map();
}
// 单文件上传
async uploadSingleFile(file, url, options = {}) {
const formData = new FormData();
formData.append('file', file);
// 添加额外的表单字段
if (options.metadata) {
Object.entries(options.metadata).forEach(([key, value]) => {
formData.append(key, value);
});
}
const uploadId = this.generateUploadId();
try {
const response = await fetch(url, {
method: 'POST',
body: formData,
signal: options.signal
});
if (!response.ok) {
throw new Error(`上传失败: ${response.status} ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error('文件上传失败:', error);
throw error;
}
}
// 多文件并发上传
async uploadMultipleFiles(files, url, options = {}) {
const uploads = Array.from(files).map(file => ({
file,
id: this.generateUploadId(),
status: 'pending'
}));
const results = [];
const semaphore = new Semaphore(this.maxConcurrent);
const uploadPromises = uploads.map(async (upload) => {
await semaphore.acquire();
try {
upload.status = 'uploading';
const result = await this.uploadSingleFile(upload.file, url, {
...options,
metadata: {
...options.metadata,
uploadId: upload.id,
fileName: upload.file.name
}
});
upload.status = 'completed';
upload.result = result;
return { success: true, upload, result };
} catch (error) {
upload.status = 'failed';
upload.error = error;
return { success: false, upload, error };
} finally {
semaphore.release();
}
});
const allResults = await Promise.allSettled(uploadPromises);
return {
total: uploads.length,
successful: allResults.filter(r => r.status === 'fulfilled' && r.value.success).length,
failed: allResults.filter(r => r.status === 'rejected' || !r.value.success).length,
results: allResults.map(r => r.status === 'fulfilled' ? r.value : { success: false, error: r.reason })
};
}
// 大文件分块上传
async uploadLargeFile(file, url, options = {}) {
const chunks = this.createFileChunks(file);
const uploadId = this.generateUploadId();
const uploadedChunks = [];
console.log(`开始分块上传: ${file.name}, 总块数: ${chunks.length}`);
try {
// 初始化上传会话
const initResponse = await fetch(`${url}/init`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
fileName: file.name,
fileSize: file.size,
totalChunks: chunks.length,
uploadId: uploadId
})
});
if (!initResponse.ok) {
throw new Error('初始化上传失败');
}
const { sessionId } = await initResponse.json();
// 上传每个分块
for (let i = 0; i < chunks.length; i++) {
const chunk = chunks[i];
const chunkFormData = new FormData();
chunkFormData.append('chunk', chunk);
chunkFormData.append('chunkIndex', i.toString());
chunkFormData.append('sessionId', sessionId);
chunkFormData.append('uploadId', uploadId);
const chunkResponse = await fetch(`${url}/chunk`, {
method: 'POST',
body: chunkFormData,
signal: options.signal
});
if (!chunkResponse.ok) {
throw new Error(`分块 ${i} 上传失败`);
}
uploadedChunks.push(i);
// 触发进度回调
if (options.onProgress) {
options.onProgress({
loaded: uploadedChunks.length,
total: chunks.length,
percentage: (uploadedChunks.length / chunks.length) * 100,
uploadId: uploadId
});
}
}
// 完成上传
const completeResponse = await fetch(`${url}/complete`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
sessionId: sessionId,
uploadId: uploadId
})
});
if (!completeResponse.ok) {
throw new Error('完成上传失败');
}
return await completeResponse.json();
} catch (error) {
console.error('大文件上传失败:', error);
// 尝试清理未完成的上传
try {
await fetch(`${url}/cleanup`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ uploadId })
});
} catch (cleanupError) {
console.error('清理上传失败:', cleanupError);
}
throw error;
}
}
createFileChunks(file) {
const chunks = [];
let offset = 0;
while (offset < file.size) {
const chunk = file.slice(offset, offset + this.chunkSize);
chunks.push(chunk);
offset += this.chunkSize;
}
return chunks;
}
generateUploadId() {
return `upload_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
}
// 信号量实现(控制并发数)
class Semaphore {
constructor(max) {
this.max = max;
this.current = 0;
this.queue = [];
}
async acquire() {
if (this.current < this.max) {
this.current++;
return;
}
return new Promise(resolve => {
this.queue.push(resolve);
});
}
release() {
this.current--;
if (this.queue.length > 0) {
this.current++;
const resolve = this.queue.shift();
resolve();
}
}
}
// 使用示例
const uploader = new FileUploader();
// 单文件上传
document.getElementById('fileInput').addEventListener('change', async (event) => {
const file = event.target.files[0];
if (file) {
try {
const result = await uploader.uploadSingleFile(file, '/api/upload', {
metadata: {
category: 'documents',
userId: '123'
}
});
console.log('上传成功:', result);
} catch (error) {
console.error('上传失败:', error);
}
}
});
// 多文件上传
document.getElementById('multiFileInput').addEventListener('change', async (event) => {
const files = event.target.files;
if (files.length > 0) {
const result = await uploader.uploadMultipleFiles(files, '/api/upload');
console.log('批量上传结果:', result);
}
});Stream流处理是Fetch API的强大特性,支持处理大量数据而不会导致内存溢出:
// Stream流处理的完整实现
class StreamProcessor {
constructor() {
this.activeStreams = new Map();
}
// 下载大文件并显示进度
async downloadWithProgress(url, onProgress) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`下载失败: ${response.status}`);
}
const contentLength = response.headers.get('content-length');
const total = contentLength ? parseInt(contentLength, 10) : 0;
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: total > 0 ? (loaded / total) * 100 : 0
});
}
}
// 合并所有chunks
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
const result = new Uint8Array(totalLength);
let offset = 0;
for (const chunk of chunks) {
result.set(chunk, offset);
offset += chunk.length;
}
return result;
} catch (error) {
console.error('下载失败:', error);
throw error;
}
}
// 创建自定义可读流
createReadableStream(data) {
let index = 0;
return new ReadableStream({
start(controller) {
console.log('流开始');
},
pull(controller) {
if (index < data.length) {
controller.enqueue(data[index]);
index++;
} else {
controller.close();
}
},
cancel(reason) {
console.log('流被取消:', reason);
}
});
}
// 流数据转换
async transformStream(response, transformer) {
const reader = response.body.getReader();
const decoder = new TextDecoder();
return new ReadableStream({
async start(controller) {
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
controller.close();
break;
}
// 解码数据
const text = decoder.decode(value, { stream: true });
// 应用转换器
const transformed = transformer(text);
// 输出转换后的数据
controller.enqueue(new TextEncoder().encode(transformed));
}
} catch (error) {
controller.error(error);
}
}
});
}
// 处理JSON流数据
async processJSONStream(url, onData) {
const response = await fetch(url);
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// 处理完整的JSON对象
let newlineIndex;
while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
const line = buffer.slice(0, newlineIndex);
buffer = buffer.slice(newlineIndex + 1);
if (line.trim()) {
try {
const data = JSON.parse(line);
onData(data);
} catch (error) {
console.error('JSON解析错误:', error, 'Line:', line);
}
}
}
}
// 处理剩余的buffer
if (buffer.trim()) {
try {
const data = JSON.parse(buffer);
onData(data);
} catch (error) {
console.error('最后的JSON解析错误:', error);
}
}
} catch (error) {
console.error('流处理错误:', error);
throw error;
} finally {
reader.releaseLock();
}
}
}
// 使用示例
const streamProcessor = new StreamProcessor();
// 下载大文件并显示进度
streamProcessor.downloadWithProgress('/api/large-file', (progress) => {
console.log(`下载进度: ${progress.percentage.toFixed(2)}%`);
// 更新UI进度条
document.getElementById('progressBar').style.width = `${progress.percentage}%`;
})
.then(data => {
console.log('下载完成,文件大小:', data.length);
// 处理下载的数据
})
.catch(error => {
console.error('下载失败:', error);
});
// 处理实时JSON数据流
streamProcessor.processJSONStream('/api/realtime-data', (data) => {
console.log('接收到实时数据:', data);
// 更新UI显示实时数据
});Stream处理的核心要点:
AbortController提供了取消Fetch请求的标准方法,是现代Web应用资源管理的重要工具:
// 🎉 请求取消和生命周期管理
class RequestManager {
constructor() {
this.activeRequests = new Map();
this.requestTimeouts = new Map();
}
// 创建可取消的请求
async createCancellableRequest(url, options = {}) {
const controller = new AbortController();
const requestId = this.generateRequestId();
// 设置超时
const timeout = options.timeout || 10000;
const timeoutId = setTimeout(() => {
controller.abort();
console.log(`请求 ${requestId} 超时被取消`);
}, timeout);
// 保存请求信息
this.activeRequests.set(requestId, {
controller,
url,
startTime: Date.now()
});
this.requestTimeouts.set(requestId, timeoutId);
try {
const response = await fetch(url, {
...options,
signal: controller.signal
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return {
requestId,
response,
data: await response.json()
};
} catch (error) {
if (error.name === 'AbortError') {
console.log(`请求 ${requestId} 被取消`);
throw new Error('请求被取消');
}
throw error;
} finally {
// 清理资源
this.cleanup(requestId);
}
}
// 取消特定请求
cancelRequest(requestId) {
const request = this.activeRequests.get(requestId);
if (request) {
request.controller.abort();
console.log(`手动取消请求: ${requestId}`);
return true;
}
return false;
}
// 取消所有活动请求
cancelAllRequests() {
const cancelledCount = this.activeRequests.size;
for (const [requestId, request] of this.activeRequests) {
request.controller.abort();
}
console.log(`取消了 ${cancelledCount} 个活动请求`);
return cancelledCount;
}
// 获取活动请求状态
getActiveRequestsStatus() {
const status = [];
for (const [requestId, request] of this.activeRequests) {
status.push({
requestId,
url: request.url,
duration: Date.now() - request.startTime,
signal: request.controller.signal
});
}
return status;
}
// 清理请求资源
cleanup(requestId) {
this.activeRequests.delete(requestId);
const timeoutId = this.requestTimeouts.get(requestId);
if (timeoutId) {
clearTimeout(timeoutId);
this.requestTimeouts.delete(requestId);
}
}
generateRequestId() {
return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
// 创建请求组(可以批量取消)
createRequestGroup() {
const groupController = new AbortController();
const groupId = this.generateRequestId();
return {
groupId,
signal: groupController.signal,
abort: () => groupController.abort(),
// 创建组内请求
createRequest: async (url, options = {}) => {
return await fetch(url, {
...options,
signal: groupController.signal
});
}
};
}
}
// 使用示例
const requestManager = new RequestManager();
// 创建可取消的请求
async function fetchUserData() {
try {
const result = await requestManager.createCancellableRequest('/api/users', {
timeout: 5000
});
console.log('用户数据:', result.data);
return result.data;
} catch (error) {
console.error('获取用户数据失败:', error);
}
}
// 创建请求组
const searchGroup = requestManager.createRequestGroup();
async function performSearch(query) {
try {
const response = await searchGroup.createRequest(`/api/search?q=${query}`);
const data = await response.json();
console.log('搜索结果:', data);
} catch (error) {
if (error.name === 'AbortError') {
console.log('搜索被取消');
} else {
console.error('搜索失败:', error);
}
}
}
// 用户输入时取消之前的搜索
let currentSearch = null;
document.getElementById('searchInput').addEventListener('input', (event) => {
// 取消之前的搜索
if (currentSearch) {
currentSearch.abort();
}
// 创建新的搜索组
currentSearch = requestManager.createRequestGroup();
// 执行新搜索
performSearch(event.target.value);
});
// 页面卸载时取消所有请求
window.addEventListener('beforeunload', () => {
requestManager.cancelAllRequests();
});AbortController的核心要点:
💼 实际开发经验:在单页应用中,合理使用AbortController可以显著提升应用性能和用户体验,特别是在路由切换和搜索场景中。
通过本节Fetch高级功能详解的学习,你已经掌握:
A: 实现断点续传功能,记录已上传的分块,网络恢复后从中断点继续上传。可以使用localStorage保存上传状态。
A: 不会。Stream的设计就是为了避免内存问题,数据是分块处理的,不会一次性加载到内存中。
A: AbortController只能取消客户端的请求处理,无法阻止已经发送到服务器的请求。服务器端需要实现相应的取消机制。
A: Fetch API本身不支持上传进度监控,需要使用XMLHttpRequest或者实现分块上传来模拟进度。
A: 适合大文件、实时数据、日志流、视频流等需要连续处理的数据类型。
// 问题:大文件上传经常失败
// 解决:实现重试机制和错误恢复
class RobustUploader {
async uploadWithRetry(file, url, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await this.uploadFile(file, url);
} catch (error) {
console.log(`上传尝试 ${attempt} 失败:`, error);
if (attempt === maxRetries) {
throw new Error(`上传失败,已重试 ${maxRetries} 次`);
}
// 指数退避延迟
await this.delay(Math.pow(2, attempt) * 1000);
}
}
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}// 问题:Stream处理导致内存泄漏
// 解决:正确释放Stream资源
class SafeStreamProcessor {
async processStream(response) {
const reader = response.body.getReader();
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
// 处理数据
this.processChunk(value);
}
} finally {
// 确保释放reader锁
reader.releaseLock();
}
}
}"掌握Fetch的高级功能让你能够构建企业级的Web应用。继续探索现代Web技术的前沿,你将成为全栈开发的技术专家!"