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.