
Axios 进度捕获实战onUploadProgress 与 onDownloadProgress 在浏览器和 Node.js 中的实现原理【免费下载链接】axiosPromise based HTTP client for the browser and node.js项目地址: https://gitcode.com/GitHub_Trending/ax/axios本文基于 axios 官方文档 Progress capturing 展开讲解如何在浏览器与 Node.js 两种环境中捕获上传/下载进度事件、理解AxiosProgressEvent各字段的含义与默认值并结合 进度事件归约器、节流工具 与 xhr、http、fetch 三个适配器的源码深入剖析每秒 3 次的节流机制、速率/剩余时间估算算法以及 Node.js 下流式上传时maxRedirects: 0的必要性。读完本文你可以完整掌握 axios 进度回调的接入方式、字段语义和底层触发链路并能在真实项目中正确实现大文件上传/下载的进度条。进度事件能提供什么AxiosProgressEvent 字段全解axios 在浏览器和 Node 环境中都支持捕获请求的上传/下载进度。官方文档给出的最小可用示例是await axios.post(url, data, { onUploadProgress: function (axiosProgressEvent) { /*{ loaded: number; total?: number; progress?: number; // in range [0..1] bytes: number; // how many bytes have been transferred since the last trigger (delta) estimated?: number; // estimated time in seconds rate?: number; // upload speed in bytes upload: true; // upload sign }*/ }, onDownloadProgress: function (axiosProgressEvent) { /*{ loaded: number; total?: number; progress?: number; bytes: number; estimated?: number; rate?: number; // download speed in bytes download: true; // download sign }*/ }, });结合 TypeScript 类型定义 index.d.ts 中的AxiosProgressEvent接口L363-L374与源码 progressEventReducer.js 中实际构造的对象完整的字段语义如下字段类型必选含义与说明loadednumber是当前已传输的字节数。源码会将其钳制在[0, total]区间避免个别环境下loaded越界totalnumber否总字节数。仅当传输方可计算长度如设置了Content-Length/ 响应带Content-Length时才存在progressnumber否进度比例取值范围[0..1]等于loaded / total没有total时该字段为undefinedbytesnumber是自上次触发以来新传输的字节数增量 delta不是累计值ratenumber否传输速率单位 bytes/秒由速率计speedometer计算得出estimatednumber否按当前速率估算的剩余时间秒等于(total - loaded) / raterate或total缺失时无此字段uploadboolean否上传标记。仅上传进度事件携带upload: truedownloadboolean否下载标记。仅下载进度事件携带download: trueeventany否原始底层事件对象浏览器为原生ProgressEventNode 下为装饰后的事件类型定义中记为BrowserProgressEventlengthComputableboolean是本次事件能否计算总长即total ! null的布尔结果可以看到官方文档注释里的字段是核心子集而类型定义与源码还额外暴露了event和lengthComputable方便在回调里判断是否处于可计算进度状态。核心机制之一强制 3 次/秒的节流官方文档明确指出The frequency of progress events is forced to be limited to 3 times per second. This is to prevent the browser from being overwhelmed with progress events.进度事件频率被强制限制为每秒 3 次以防浏览器被进度事件淹没。这一限制在 progressEventReducer.js 中实现export const progressEventReducer (listener, isDownloadStream, freq 3) { let bytesNotified 0; const _speedometer speedometer(50, 250); return throttle((e) { // ... 计算 loaded / total / bytes / rate / estimated 并调用 listener(data) }, freq); };三个关键点默认频率freq 3且三个适配器xhr/http/fetch在 Node 侧均以字面量3显式传入浏览器侧使用默认值。也就是说每秒 3 次是硬性约定不暴露配置项。节流不是简单丢弃。throttle.js 返回一个二元组[throttled, flush]throttled在时间阈值1000 / freq毫秒内合并调用、只保留最新参数并挂一个setTimeout延迟补发flush则立即把最后一次被合并的参数冲刷出去。适配器在上传流结束时调用flush见下文保证进度条最终停在 100%而不是停在最后一次节流时刻。增量bytes由bytesNotified维护progressBytes max(0, loaded - bytesNotified)bytesNotified单调不减即使个别环境事件乱序或回退也不会出现负增量。浏览器 xhr 适配器中的接线方式见 xhr.js L199-L212// Handle progress if needed if (onDownloadProgress) { [downloadThrottled, flushDownload] progressEventReducer(onDownloadProgress, true); request.addEventListener(progress, downloadThrottled); } // Not all browsers support upload events if (onUploadProgress request.upload) { [uploadThrottled, flushUpload] progressEventReducer(onUploadProgress); request.upload.addEventListener(progress, uploadThrottled); request.upload.addEventListener(loadend, flushUpload); }注意两个细节下载进度监听在XMLHttpRequest本体的progress事件上isDownloadStream true因此事件带download: true标记上传进度依赖request.uploadUpload对象源码用request.upload的存在性做了特性探测——Not all browsers support upload eventsloadend时执行flushUpload把最后一次被节流合并的上传事件强制补发保证收尾事件不丢失。核心机制之二速率与剩余时间估算speedometerrate和estimated字段由 speedometer.js 提供。它的构造参数在progressEventReducer中固定为speedometer(50, 250)samplesCount 50内部是一个环形缓冲保存最近 50 个样本每次push记录一个自上次触发以来的字节数和时间戳min 250采样窗口不足 250 毫秒时不返回速率if (now - firstSampleTS min) return;避免传输刚开始、样本太少时出现剧烈抖动的假速率。一旦满足最小窗口速率按Math.round((bytesCount * 1000) / passed)计算即窗口内累计字节数 / 窗口经过的毫秒数 × 1000单位 bytes/s。随后estimated (total - loaded) / rate直接由progressEventReducer组合得出。这也解释了为什么文档中rate、estimated均为可选字段冷启动阶段或total未知时它们就是undefined。Node.js 环境流式上传与进度事件官方文档的第二段展示了 Node.js 中把上传进度事件流式化消费的典型场景——上传一个可读流并实时打印百分比const { data } await axios.post(SERVER_URL, readableStream, { onUploadProgress: ({ progress }) { console.log((progress * 100).toFixed(2)); }, headers: { Content-Length: contentLength, }, maxRedirects: 0, // avoid buffering the entire stream });这段示例有三个实操要点全部与源码对应手动声明Content-Length才能算出progress。progress loaded / total而total来自请求的Content-Length头。Node 侧 http 适配器在 http.js L845、L874-L878 中正是用utils.toFiniteNumber(headers.getContentLength())取出contentLength再交给progressEventDecorator包装const contentLength utils.toFiniteNumber(headers.getContentLength()); // ... onUploadProgress data.on( progress, flushOnFinish( data, progressEventDecorator( contentLength, progressEventReducer(asyncDecorator(onUploadProgress, scheduleProgress), false, 3) ) ) );不声明该头时total为undefined回调里只有loaded、bytes可用progress为undefined。上传数据被包装为带progress事件的流管道。Node 侧并非直接监听字节数而是把请求体经过stream.pipeline([data, new AxiosTransformStream({ maxRate })])http.js L859-L867由转换流按块发出progress事件再经asyncDecorator(onUploadProgress, scheduleProgress)调度到异步回调中执行避免在流事件循环中阻塞事件循环。maxRedirects: 0强烈建议保留。文档中的危险提示原文是It is recommended to disable redirects by setting maxRedirects: 0 to upload the stream in the node.js environment, as the follow-redirects package will buffer the entire stream in RAM without following the backpressure algorithm.即Node 的 http 适配器处理重定向依赖follow-redirects该包在重传同一个请求体时会把整个流缓冲进内存且不遵守 backpressure背压算法——对大文件流式上传意味着内存可能被撑爆。若目标地址不会发生重定向例如自己控制的服务端设maxRedirects: 0可以让流真正逐块发送。下载侧同理onDownloadProgress的进度来自响应流上串联的AxiosTransformStreamhttp.js L1125-L1147total取自响应头content-length。环境差异与限制文档中还有一个必须知道的限制warning 级别Capturing FormData upload progress is not currently supported in node.js environments.即在 Node.js 环境中基于FormData的上传目前无法捕获进度。从源码结构看这与 Node 侧进度依赖stream.Readable包装链路有关http 适配器对非流数据会执行stream.Readable.from(data)而 Node 的FormData体本身并不经过这条发出progress事件的管道因此拿不到增量字节数。如果你的 Node 场景需要 FormData 上传 进度条可行的替代思路是自行构造multipart/form-data的可读流同时手动设置Content-Length而不是直接传FormData实例。fetch 适配器同样支持进度上传侧通过trackRequestStream(_request.body, onProgress, flush)fetch.js L380-L390跟踪请求体流下载侧在 L526-L528 用progressEventDecorator progressEventReducer(asyncDecorator(onDownloadProgress), true)包装响应流且都使用asyncDecorator把回调调度到微任务/异步队列与 http 适配器保持同一套事件语义。相关配置与测试验证速率上限index.d.ts中定义了maxUploadRate/maxDownloadRatetype MaxUploadRate numberhttp 适配器会把maxRate配置可为[upload, download]数组交给AxiosTransformStream({ maxRate })http.js L847-L864用于在限速的同时仍能产出进度事件——onUploadProgress || maxUploadRate满足其一即建立进度管道。浏览器端行为测试tests/browser/progress.browser.test.js 覆盖了 xhr 适配器的上/下载进度与节流场景。Node 端行为测试tests/unit/adapters/http.test.jsL4958、L5027、L5137 等多处逐一断言了loaded、total、progress、bytes、rate、upload/download各字段的实际值tests/unit/adapters/fetch.test.js 验证 fetch 适配器tests/smoke/esm/tests/progress.smoke.test.js 等 smoke 测试覆盖跨环境的基本链路。小结进度捕获通过请求配置中的onUploadProgress/onDownloadProgress接入事件对象在三种适配器xhr / http / fetch中语义一致事件频率被progressEventReducer强制节流到3 次/秒并用throttle的flush机制保证收尾事件不丢失progress、rate、estimated均为可选字段取决于Content-Length是否存在与采样窗口是否达到 250msNode.js 流式上传建议显式声明Content-Length并设置maxRedirects: 0避免 follow-redirects 整流缓冲Node 环境下FormData上传暂不支持进度捕获。以上机制均可在 lib/helpers/progressEventReducer.js、lib/helpers/throttle.js、lib/helpers/speedometer.js 与三个适配器源码中直接查证。【免费下载链接】axiosPromise based HTTP client for the browser and node.js项目地址: https://gitcode.com/GitHub_Trending/ax/axios创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考