Tratando dados em JavaScript de maneira similar ao operador pipe da linha de comando

Uma das funcionalidades mais formidáveis da interface de linha de comando é o operador pipe. A possibilidade de utilizar os dados resultantes de uma operação como dados de entrada para a operação seguinte torna o processo intuitivo, além de abreviar consideravelmente a quantidade de código escrito, tornando o fluxo de desenvolvimento extremamente produtivo.

Veja a sequência de comandos a seguir, por exemplo. Ela lê o conteúdo de um arquivo chamado contatos.txt, ordena-o, remove os contatos duplicados e, por fim, salva o resultado em um novo arquivo chamado contatos_unicos_ordenados.txt.

cat ./contatos.txt | sort | uniq > ./contatos_unicos_ordenados.txt

Uma operação relativamente comum no desenvolvimento de websites é a conversão de um texto em um trecho de URL (slug). Para alcançar esse resultado, precisamos garantir que eventuais acentos sejam trocados por seus equivalentes caracteres sem acento, espaços desnecessários e caracteres especiais sejam removidos e, por fim, espaços sejam substituídos por hífens. Por exemplo, para a entrada "== As crianças ainda acreditam em Papai Noel? ==" teríamos "as-criancas-ainda-acreditam-em-papai-noel".

A implementação desse tipo de tratamento costuma ser um passo a passo semelhante a esse:

function buildSlug(text){
  let slug = text.toLowerCase().trim();
  slug = removeAccents(slug);
  slug = removeUnnecessarySpaces(slug);
  slug = removeSpecialChars(slug);
  return replaceSpacesWithDashes(slug);
}

Embora a solução seja legível e intuitiva, o código não é nada sucinto. Além disso, uma mesma variável tem seu valor reatribuído três vezes ao longo do processo, uma abordagem que não é considerada bacana do ponto de vista funcional.

Uma alternativa a essa solução seria usarmos o resultado de uma operação como entrada da operação seguinte de maneira direta, sem intermediários, ou seja, sem necessitarmos de uma variável temporária de estado:

function buildSlug(text){
  return replaceSpacesWithDashes(
    removeSpecialChars(
      removeUnnecessarySpaces(
        removeAccents(
          text.toLowerCase().trim()
        )
      )
    )
  );
}

Temos agora um código bem mais compacto e funcional, mas a legibilidade e a intuitividade sofreram forte degradação. Ao invés de simplesmente lermos as operações linha a linha, precisamos avançar a leitura até a função removeAccents para, só depois, entender que seu retorno alimentará a função removeUnnecessarySpaces e assim por diante.

Haveria então uma terceira abordagem capaz de reunir legibilidade e brevidade? Há algum tempo tenho percebido o tremendo poder oferecido pelo método nativo reduce do JavaScript. Percebi então que para casos como o descrito aqui ele pode ser uma ótima opção. Para tal, reduzimos um dado inicial ao resultado dessa cadeia de operações:

function buildSlug(text){
  return [
    removeAccents,
    removeUnnecessarySpaces,
    removeSpecialChars,
    replaceSpacesWithDashes
  ].reduce((result, perform) => perform(result), text.toLowerCase().trim());
}

Com essa solução, todas as operações são facilmente identificadas de maneira sequencial numa lista, melhorando consideravelmente a legibilidade do código e, na sequência, cada operação é executada recebendo como dado de entrada exatamente o dado resultante da operação anterior, sem a necessidade de qualquer variável de estado e expressada por apenas uma única linha de código.

Caso você tenha se interessado pelo poder do método reduce, visite sua documentação e conheça melhor seus detalhes e formas de uso.