← Recipes

阅读中文版本

Add a Progress Bar for Streamed Work

Render progress to stderr so it does not pollute stdout, and suppress it when output is not a terminal.

~20 mincliuxterminal

Task

Add a progress indicator that writes to stderr and degrades when piped.

When to use this

A long import prints progress to stdout, corrupting the machine-readable output downstream.

Prerequisites

  • A CLI performing iterative work
  • Access to stderr
  • TTY detection

Steps

1. Render progress on stderr only when interactive.

const show = process.stderr.isTTY;
function progress(done, total) {
  if (!show) return;
  const pct = Math.floor((done / total) * 100);
  process.stderr.write(`\r${pct}% (${done}/${total})`);
}

Expected output: Progress updates overwrite themselves on stderr without touching stdout.

2. Clear the line when finished.

function finish() { if (show) process.stderr.write('\n'); }

Expected output: The final newline keeps the terminal prompt clean.

Troubleshooting

Problem

Progress in logs

Solution

Only render when stderr is a TTY; log a periodic summary otherwise.

Problem

Carriage returns corrupt output

Solution

Never write progress to stdout; keep data and UI separated.

Limitations

  • Rendering per item is costly; throttle updates.
  • Not accessible to screen readers.
  • Does not estimate remaining time well for uneven work.

What this recipe does

Add a progress indicator that writes to stderr and degrades when piped.

A long import prints progress to stdout, corrupting the machine-readable output downstream.

Prerequisites

  • A CLI performing iterative work
  • Access to stderr
  • TTY detection

Procedure

  1. Render progress on stderr only when interactive.
const show = process.stderr.isTTY;
function progress(done, total) {
  if (!show) return;
  const pct = Math.floor((done / total) * 100);
  process.stderr.write(`\r${pct}% (${done}/${total})`);
}

Expected result: Progress updates overwrite themselves on stderr without touching stdout.

  1. Clear the line when finished.
function finish() { if (show) process.stderr.write('\n'); }

Expected result: The final newline keeps the terminal prompt clean.

How to confirm it worked

Run the commands above and confirm every expected result in order. If a step does not produce the stated output, use the troubleshooting notes before continuing. A passing run shows interactive runs show an updating bar on stderr, while piped runs keep stdout clean.

Troubleshooting

Progress in logs — Only render when stderr is a TTY; log a periodic summary otherwise.

Carriage returns corrupt output — Never write progress to stdout; keep data and UI separated.

Limitations

  • Rendering per item is costly; throttle updates.
  • Not accessible to screen readers.
  • Does not estimate remaining time well for uneven work.