diessi.caBlog
August 06, 2016

Encadeamento de Métodos em JavaScript

Popular em diversas bibliotecas JavaScript, o encadeamento de métodos (“method chaining”) é uma técnica usada para invocar diversos métodos em um mesmo objeto.

Com o objetivo de melhorar a legibilidade do código, a técnica é vastamente utilizada na API da jQuery, o que certamente influenciou na popularidade da biblioteca.

Se você já utilizou jQuery, o estilo do código abaixo pode ser familiar:

$("#my-element")
  .css("background", "purple")
  .height(100)
  .animate({ height: 250 })
  .find("input")
  .fadeIn(200)
  .val("")
  .end();

No exemplo acima, uma única declaração faz várias coisas. No entanto, uma boa prática com method chaining é fazer somente uma ação por declaração. ;-)

Perceba que vários métodos são invocados no objeto $('#my-element'), sem a necessidade de repetí-lo. Já sem Method Chaining, é necessário fazer a referência diversas vezes:

const myElement = $("#my-element");
myElement.css("background", "purple");
myElement.height(100);
myElement.fadeIn(200);

Exemplo

Vamos criar um contador Counter:

class Counter {
  constructor() {
    this.value = 0;
  }

  increase() {
    this.value += 1;
  }

  decrease() {
    this.value -= 1;
  }

  log() {
    console.log(this.value);
  }
}

Agora, vamos instanciar um contador e usar seus métodos:

const counter = new Counter();
counter.increase();
counter.log(); // => 1
counter.decrease();
counter.log(); // => 0

Perceba que é necessário fazer várias declarações para interagir com a instância, o que prejudica a legibilidade do código.

E se tentarmos usar Method Chaining

new Counter().increase().log();
// > TypeError: Cannot read property 'log' of undefined

Perceba que log() está sendo executado em new Counter().increase(), que, por sua vez, está retornando undefined. Portanto, ainda não é possível interagir com Counter dessa forma.

Como Encadear Métodos

Para evitar a repetição do objeto, é necessário que seus métodos retornem o próprio objeto.

Veja este exemplo com Promises:

getJSON("users.json")
  .then(JSON.parse)
  .then((response) => console.log("Olha o JSON!", response))
  .catch((error) => console.log("Falhou!", error));

Isso só é possível pois os métodos then() and catch() sempre retornam outras promises. Assim, podemos dizer que as Promises são fluent APIs, tal como a jQuery.

Quem Lembra do this?

Para os métodos serem encadeados, será necessário retornar o contexto (this) em cada método.

Em JavaScript, this sempre se refere ao contexto de execução de função.

No caso de um método, que é uma função de um objeto, refere-se ao próprio objeto.

Exemplo com Method Chaining Pattern

Para implementar o encadeamento de métodos na classe Counter, apenas retornamos seu contexto a cada método:

class Counter {
  constructor() {
    this.value = 0;
  }
  increase() {
    this.value += 1;
    return this; // Aqui!
  }
  decrease() {
    this.value -= 1;
    return this; // Aqui!
  }
  log() {
    console.log(this.value);
    return this; // E aqui!
  }
}

Agora, ao executar new Counter().increase(), o retorno já não será mais undefined.

…E, portanto, é possível fazer method chaining!

new Counter()
  .increase()
  .log() // => 1
  .decrease()
  .log(); // => 0

Conclusão

No universo de APIs orientadas a objetos, o encadeamento de métodos é uma técnica incrível se o seu objetivo é tornar o código mais expressivo e fluente.

No geral, fluent APIs são sim interessantes de se entender e implementar, e você pode ter certeza disso analisando o primeiro exemplo com jQuery do início deste artigo. É fantástico! Mas é importante entender que o encadeamento de métodos nem sempre tornará as coisas mais fáceis (debugar, por exemplo, se torna mais difícil), e, portanto, a maneira aparentemente “mágica” com que elas funcionam não deve ser sempre levada em consideração.