Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

Readline Promises no Node.js

Atualizado em: 16 de agosto de 2026

Terminal Linux usado em desenvolvimento e administração de aplicações Node.js

O Readline Promises no Node.js oferece uma interface baseada em Promises para ler entrada do terminal, fazer perguntas, processar linhas de arquivos e controlar o cursor. Ele pertence ao módulo node:readline/promises e simplifica CLIs que antes precisavam envolver callbacks manualmente.

A API funciona sobre streams de entrada e saída. Em terminais interativos, ela suporta prompt, histórico, sinais, edição e recursos de cursor. Em arquivos ou pipes, pode ser usada com iteração assíncrona para processar conteúdo linha por linha com baixo consumo de memória.

Neste guia, você aprenderá a criar uma interface, usar question(), AbortSignal, histórico, validação, senhas, iteração assíncrona, arquivos grandes, cursor, cleanup, testes e segurança.

O que é node:readline/promises?

É a versão baseada em Promises do módulo Readline. A documentação oficial de Readline Promises descreve classes e métodos. A documentação oficial de TTY explica terminais, modos e dimensões.

Para a API tradicional, consulte Readline no Node.js. Para terminal, veja TTY no Node.js. O artigo de Console no Node.js ajuda a separar stdout e stderr.

Importando

const readline = require('node:readline/promises');
const {
  stdin: input,
  stdout: output
} = require('node:process');

Criando a interface

const rl = readline.createInterface({
  input,
  output
});

Feche a interface ao terminar para liberar listeners e permitir o encerramento do processo.

Pergunta básica

try {
  const name = await rl.question('Seu nome: ');
  console.log(`Olá, ${name}!`);
} finally {
  rl.close();
}

question() resolve com o texto digitado sem a quebra de linha.

Validação

async function askRequired(question) {
  while (true) {
    const answer = (await rl.question(question)).trim();

    if (answer.length > 0) {
      return answer;
    }

    output.write('Valor obrigatório.\n');
  }
}

Defina limite de tentativas quando a CLI roda em automação.

Números

async function askInteger(question) {
  const answer = await rl.question(question);
  const value = Number(answer);

  if (!Number.isInteger(value)) {
    throw new Error('Número inteiro inválido');
  }

  return value;
}

Não use parseInt sem verificar o restante da string quando o formato precisa ser estrito.

Confirmação

async function confirm(question) {
  const answer = (await rl.question(
    `${question} [s/N] `
  )).trim().toLowerCase();

  return answer === 's' || answer === 'sim';
}

Use padrão seguro para operações destrutivas: ausência de resposta deve significar cancelamento.

Timeout com AbortSignal

const signal = AbortSignal.timeout(30000);

try {
  const answer = await rl.question(
    'Resposta: ',
    { signal }
  );
} catch (error) {
  if (error.name === 'AbortError') {
    console.error('Tempo esgotado');
  } else {
    throw error;
  }
}

O suporte depende da versão. Timeout evita que jobs automatizados aguardem para sempre.

Cancelamento externo

const controller = new AbortController();

process.once('SIGINT', () => {
  controller.abort();
});

await rl.question('Continuar? ', {
  signal: controller.signal
});

Trate Ctrl+C e encerre com código apropriado.

Sinais

A interface pode emitir eventos relacionados a SIGINT e outros sinais em terminais compatíveis. Não presuma o mesmo comportamento em Windows, pipes e ambientes CI.

terminal

const rl = readline.createInterface({
  input,
  output,
  terminal: output.isTTY
});

Quando stdout não é TTY, desabilitar recursos interativos evita códigos ANSI em logs.

CLIs em pipeline

Uma ferramenta pode receber dados por pipe:

cat users.txt | node cli.js

Nesse modo, não mostre prompts que contaminem stdout. Use stderr para mensagens humanas.

Separando stdout e stderr

process.stdout.write(JSON.stringify(result));
process.stderr.write('Processamento concluído\n');

Isso permite encadear a saída em outras ferramentas.

Processando linhas

const rl = readline.createInterface({
  input,
  crlfDelay: Infinity
});

for await (const line of rl) {
  await processLine(line);
}

A iteração assíncrona respeita o fluxo de processamento e é adequada para arquivos grandes.

Lendo arquivo grande

const fs = require('node:fs');

const file = fs.createReadStream('./data.txt', {
  encoding: 'utf8'
});

const rl = readline.createInterface({
  input: file,
  crlfDelay: Infinity
});

for await (const line of rl) {
  if (line.length > 10000) {
    throw new Error('Linha muito longa');
  }

  await processLine(line);
}

Consulte File System no Node.js.

crlfDelay

Infinity ajuda a tratar CRLF como uma única quebra mesmo com atraso entre caracteres. É uma opção comum para arquivos multiplataforma.

Linhas vazias

Decida se linha vazia é registro válido, separador ou erro. Não aplique trim automaticamente quando espaços fazem parte do formato.

Backpressure

O loop for await aguarda o processamento antes de pedir a próxima linha. Ainda assim, a stream possui buffers internos. Não acumule todas as linhas em um array.

Concorrência limitada

Se cada linha inicia I/O, uma execução estritamente sequencial pode ser lenta. Use um pool com limite:

const pending = new Set();
const concurrency = 10;

for await (const line of rl) {
  const task = processLine(line)
    .finally(() => pending.delete(task));

  pending.add(task);

  if (pending.size >= concurrency) {
    await Promise.race(pending);
  }
}

await Promise.all(pending);

Erros no loop

Quando processLine falha, feche a interface e destrua a stream se necessário. Não continue silenciosamente sem registrar o número da linha.

Contador de linha

let lineNumber = 0;

for await (const line of rl) {
  lineNumber += 1;
  await processLine(line, lineNumber);
}

Histórico

Em terminal interativo, a interface mantém histórico conforme as opções. O histórico pode conter segredos digitados por engano.

historySize

const rl = readline.createInterface({
  input,
  output,
  historySize: 100,
  removeHistoryDuplicates: true
});

Reduza ou desabilite histórico em comandos sensíveis.

Senhas

question() exibe caracteres digitados. Para senha, use uma biblioteca confiável que desabilite echo ou implemente raw mode com extremo cuidado.

Não registre a resposta e limpe referências assim que possível.

Raw mode

if (input.isTTY) {
  input.setRawMode(true);
}

Raw mode entrega teclas imediatamente e altera o comportamento de sinais. Sempre restaure em finally:

try {
  input.setRawMode(true);
  await readSecret();
} finally {
  input.setRawMode(false);
}

Cursor

O módulo Readline possui APIs para mover cursor e limpar tela. A versão Promises também oferece uma classe de auto commit em versões compatíveis.

ReadlinePromises.ReadStream?

Não confunda a interface Readline com streams do processo. Consulte os nomes exatos na versão utilizada, porque a API evolui.

readlinePromises.ReadLine

A classe de operações de cursor pode enfileirar comandos:

const screen = new readline.Readline(output, {
  autoCommit: false
});

screen.clearLine(0);
screen.cursorTo(0);
await screen.commit();

Verifique capitalização e disponibilidade na documentação da sua versão.

Atualizando progresso

if (output.isTTY) {
  output.write(`Progresso: ${percent}%`);
  readline.cursorTo(output, 0);
}

Em logs não interativos, imprima linhas normais em intervalos maiores.

Prompt

rl.setPrompt('app> ');
rl.prompt();

A interface baseada em Promises ainda pode usar métodos tradicionais para loops interativos.

Loop de comandos

while (true) {
  const command = (
    await rl.question('app> ')
  ).trim();

  if (command === 'exit') break;
  await executeCommand(command);
}

Use uma allowlist. Nunca passe a string diretamente para shell.

Segurança de comandos

const commands = {
  status: showStatus,
  clear: clearCache
};

const handler = commands[command];

if (!handler) {
  throw new Error('Comando desconhecido');
}

await handler();

Consulte Child Process no Node.js para riscos de shell injection.

Autocomplete

A opção completer fornece sugestões. Ela deve ser rápida e não realizar rede bloqueante a cada tecla.

function completer(line) {
  const commands = ['status', 'help', 'exit'];
  const hits = commands.filter(item =>
    item.startsWith(line)
  );

  return [hits.length ? hits : commands, line];
}

Multilíngue e Unicode

Terminais variam em encoding, largura de caracteres e emojis. Teste em Windows Terminal, Linux, macOS e CI.

Dimensões

const columns = output.columns || 80;
const rows = output.rows || 24;

Não presuma que essas propriedades existem em pipes.

Resize

TTY pode emitir evento resize. Recalcule layout sem apagar informação crítica.

Cleanup

async function main() {
  const rl = readline.createInterface({ input, output });

  try {
    await runCli(rl);
  } finally {
    rl.close();
  }
}

Process.exit()

Evite chamar process.exit() imediatamente porque stdout pode não terminar de escrever. Defina process.exitCode e feche recursos.

Veja Graceful Shutdown no Node.js.

Testes

Use PassThrough para simular input e capturar output:

const {
  PassThrough
} = require('node:stream');

const input = new PassThrough();
const output = new PassThrough();

const rl = readline.createInterface({
  input,
  output,
  terminal: false
});

Testando question()

Inicie a Promise e escreva a resposta:

const answerPromise = rl.question('Nome: ');
input.write('Ana\n');
const answer = await answerPromise;

Testando timeout

Use timers controlados ou prazo pequeno e confirme que a interface continua sendo fechada.

Testando arquivos

Cubra CRLF, LF, linha final sem quebra, arquivo vazio, linha longa e erro no meio.

Erros comuns

  • Não fechar a interface: o processo permanece ativo.
  • Mostrar prompt em pipe: saída automatizada fica corrompida.
  • Ler senha com question: caracteres ficam visíveis.
  • Sem timeout: CI aguarda para sempre.
  • Acumular linhas: arquivos grandes consomem memória.
  • Comando enviado ao shell: ocorre injection.
  • Não restaurar raw mode: terminal fica quebrado.

Boas práticas

  • Feche em finally.
  • Detecte isTTY.
  • Separe stdout e stderr.
  • Use AbortSignal.
  • Valide respostas.
  • Limite tentativas.
  • Processe arquivos em stream.
  • Controle concorrência.
  • Não ecoe senhas.
  • Teste terminais e pipes.

Conclusão

O Readline Promises no Node.js simplifica perguntas interativas e processamento linha por linha usando async e await.

A API é mais confiável quando diferencia terminal de pipeline, aplica timeouts, fecha a interface e não acumula arquivos. Com validação, stdout limpo e cuidado com senhas e raw mode, é possível criar CLIs modernas que funcionam tanto para pessoas quanto em automações.

Os 10 Melhores Cursos de Programação de 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita