Node.js中使用pipe时能否借助writeStream.bytesWritten监控复制速度?
Hey there! Let's break down why you're seeing 0 for bytesWritten and how to fix this to get your progress bar, speed tracking, and remaining time estimates working properly.
What's Going Wrong Here?
pipeis asynchronous, but yourdo...whileloop runs synchronously
When you callreadableStream.pipe(firstCopy), Node.js starts the file transfer in the background—it doesn't pause the loop to wait for the transfer to start or finish. Yourconsole.log(firstCopy.bytesWritten)runs immediately after kicking off the pipe, before any bytes have actually been written. That's why you get0every time. On top of that, your loop will blast through all files at once, starting every copy task simultaneously instead of waiting for one to finish before the next.bytesWrittenworks withpipe—you just need to read it at the right time
ThebytesWrittenproperty of aWriteStreamdoes update in real-time aspipewrites data. The problem isn't the property itself, it's that you're checking it too early, before any actual writing has happened.
How to Fix It
Let's fix the async loop issue first, then add proper progress monitoring.
Step 1: Use Async/Await for Controlled File Copies
Instead of a synchronous do...while, we'll wrap the copy logic in an async function and use a regular for loop with await to wait for each file to finish copying before moving on. This gives you control over whether you copy files sequentially or in parallel.
Step 2: Track Progress in Real-Time
We'll:
- Grab the total size of the source file first (to calculate progress percentage)
- Set up an interval to check
bytesWrittenevery second - Calculate transfer speed and remaining time based on the data we collect
Full Working Example
const fs = require('fs'); const path = require('path'); const { promisify } = require('util'); // Convert callback-based fs methods to promise-based for async/await const stat = promisify(fs.stat); // Helper function to wait for a stream to finish (or error out) const waitForStream = (stream) => { return new Promise((resolve, reject) => { stream.on('finish', resolve); stream.on('error', reject); }); }; async function copyWithProgress(fileList, copyPathOne, copyPathTwo, fileName) { for (let i = 0; i < fileList.length; i++) { const sourceFile = fileList[i]; const dest1 = path.join(copyPathOne, fileName[i]); const dest2 = path.join(copyPathTwo, fileName[i]); // Get total size of the source file for progress calculations const fileStats = await stat(sourceFile); const totalBytes = fileStats.size; // Create our streams const readStream = fs.createReadStream(sourceFile); const writeStream1 = fs.createWriteStream(dest1); const writeStream2 = fs.createWriteStream(dest2); // Start piping data to both destinations readStream.pipe(writeStream1); readStream.pipe(writeStream2); const startTime = Date.now(); // Set up an interval to log progress every second const progressInterval = setInterval(() => { const written = writeStream1.bytesWritten; // Both streams should have identical progress const progressPercent = ((written / totalBytes) * 100).toFixed(2); const speedMBps = (written / 1024 / 1024 / ((Date.now() - startTime) / 1000)).toFixed(2); const remainingBytes = totalBytes - written; const remainingSeconds = remainingBytes > 0 ? (remainingBytes / (written / ((Date.now() - startTime) / 1000)) / 1000).toFixed(2) : 0; console.log(`[${fileName[i]}] Progress: ${progressPercent}% | Speed: ${speedMBps} MB/s | Remaining: ${remainingSeconds}s`); }, 1000); // Wait for both writes to finish, then clean up the interval try { await Promise.all([waitForStream(writeStream1), waitForStream(writeStream2)]); clearInterval(progressInterval); console.log(`✅ ${fileName[i]} copied successfully!`); } catch (err) { clearInterval(progressInterval); console.error(`❌ Failed to copy ${fileName[i]}:`, err); } } } // Call the function with your existing variables // copyWithProgress(fileList, copyPathOne, copyPathTwo, fileName);
Quick Notes
- Why this works: We wait for each file's copy streams to emit the
finishevent before moving to the next file, and our interval checksbytesWrittenwhile the transfer is in progress—so we get real, non-zero values. - Parallel vs Sequential: If you want to copy multiple files at once instead of one after another, you could collect all copy promises into an array and use
Promise.all()—just be mindful of system resource limits with large files. - Your earlier error:
writeStream.bytesWritten(firstCopy)failed becausebytesWrittenis a property, not a method. The correct syntax isfirstCopy.bytesWritten—you just were using it at the wrong time!
内容的提问来源于stack exchange,提问作者Puka

