A typewriter pacer takes text that arrives in uneven bursts and releases it one character at a time at a steady, even rhythm — the smooth "typing" effect you see in AI chat apps. When a model streams a reply, the network delivers text in lumps: ten characters, a pause, then thirty more. Painting each lump the instant it lands looks jittery, so a pacer buffers whatever arrives and drains that buffer at a fixed pace, giving the reader a calm, constant stream no matter how bursty the source is.
function typewriterPacer(
onChar: (char: string) => void, // called once per released character
delay?: number, // milliseconds between characters (default 20)
): {
push(text: string): void; // feed a burst of text into the buffer
flush(): void; // emit everything buffered right now
stop(): void; // cancel pending output and drop the buffer
};
const pacer = typewriterPacer((ch) => process.stdout.write(ch), 20);
pacer.push('Hello'); // 'H','e','l','l','o' released at 20ms, 40ms, 60ms...
pacer.push(' world'); // queued behind the rest — the cadence stays steady
// flush() skips the wait and emits whatever is left immediately
const pacer = typewriterPacer(onChar, 50);
pacer.push('done');
pacer.flush(); // 'd','o','n','e' all emitted now; the pending timer is cleared
delay ms, no matter how much text is waiting.push while the buffer is still draining appends to the same queue; it must not start a second, competing timer.push restarts it cleanly.onChar is your only output.