本文介绍如何通过 http `range` 请求头精准获取 url 资源的前 n 字节(如 1 mb),避免浏览器持续下载冗余数据,并解决重复调用时失效的问题。相比流式读取 + `reader.cancel()`,服务端支持的分块请求更可靠、可重入且无需手动缓冲拼接。
在前端开发中,若仅需下载远程文件的前一部分(例如校验签名、解析头部元信息或预览内容),盲目拉取完整资源不仅浪费带宽,还可能因 fetch 流未正确终止导致后续请求失败——正如原代码中反复调用 download() 后出现的异常:reader.cancel() 并不能真正中断底层网络连接,且已消耗的响应体可能影响缓存状态或服务端连接复用,造成后续请求挂起或返回不完整响应。
推荐方案:优先使用 HTTP Range 请求
现代 Web 服务器(如 Ngin

async function downloadRange(url, maxBytes) {
try {
const response = await fetch(url, {
headers: {
Range: `bytes=0-${maxBytes - 1}`
}
});
if (response.status === 206) {
// 成功获取部分数据
const arrayBuffer = await response.arrayBuffer();
const result = new Uint8Array(arrayBuffer);
const str = new TextDecoder().decode(result);
console.log(`成功下载 ${result.length} 字节:`, str.substring(0, 100) + '...');
return result;
} else if (response.status === 200) {
// 服务端不支持 Range,返回了完整响应(如小文件)
console.warn('Server does not support Range; full response received.');
const arrayBuffer = await response.arrayBuffer();
return new Uint8Array(arrayBuffer).slice(0, maxBytes);
} else {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
} catch (error) {
console.error('Download failed:', error);
throw error;
}
}✅ 优势显著:
⚠️ 注意事项与兜底策略:
async function supportsRange(url) {
const headRes = await fetch(url, { method: 'HEAD' });
return headRes.headers.get('accept-ranges')?.toLowerCase() === 'bytes';
}const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000); // 超时保护
const response = await fetch(url, { signal: controller.signal });
// ... 后续用 reader.read() + bytesRead 判断,但务必 clearTimeout(timeoutId)综上,优先采用 Range 请求是解决“下载前 N 字节”问题的最佳实践。它简洁、标准、高效且健壮。仅当明确确认目标服务不支持分块时,再考虑基于 ReadableStream 的流控方案,并务必配合 AbortController 与超时机制增强鲁棒性。