
本文介绍如何通过 http `range` 请求头精准获取 url 资源的前 n 字节(如 1 mb),避免浏览器持续下载冗余数据,并解决重复调用时失效的问题。相比流式读取 + `reader.cancel()`,服务端支持的分块请求更可靠、可重入且无需手动缓冲拼接。
在前端开发中,若仅需下载远程文件的前一部分(例如校验签名、解析头部元信息或预览内容),盲目拉取完整资源不仅浪费带宽,还可能因 fetch 流未正确终止导致后续请求失败——正如原代码中反复调用 download() 后出现的异常:reader.cancel() 并不能真正中断底层网络连接,且已消耗的响应体可能影响缓存状态或服务端连接复用,造成后续请求挂起或返回不完整响应。
推荐方案:优先使用 HTTP Range 请求
现代 Web 服务器(如 Nginx、Apache、CDN 及多数静态文件托管服务)普遍支持 Range 请求头。通过声明 Range: bytes=0-N,我们可直接向服务端申明“只需第 0 到第 N 字节”,服务端将返回 206 Partial Content 响应,且只传输指定范围的数据:
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;
}
}✅ 优势显著:
- ✅ 可重入性强:每次请求都是独立的 HTTP 事务,无 reader 状态残留;
- ✅ 零内存缓冲开销:无需 chunks 数组累积、无需手动 Uint8Array.set() 拼接;
- ✅ 服务端协同节能:真正阻止多余字节下发,降低服务器与网络负载;
- ✅ 语义清晰:符合 HTTP/1.1 规范,调试时可通过 DevTools → Network 面板直观查看 206 Partial Content 及 Content-Range 响应头。
⚠️ 注意事项与兜底策略:
-
检查服务端支持性:并非所有服务都启用 Accept-Ranges: bytes。可在首次请求前发起 HEAD 探测:
async function supportsRange(url) { const headRes = await fetch(url, { method: 'HEAD' }); return headRes.headers.get('accept-ranges')?.toLowerCase() === 'bytes'; } - 处理小文件边界:若文件总大小
-
不支持 Range 时的降级:可结合 AbortController 实现流式中断(需注意其兼容性):
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 与超时机制增强鲁棒性。










