Tornando atributos obrigatórios
Para garantir que uma função tenha como obrigatório alguns atributos definidos, e que do contrário seja lançado um erro, é possível aplicar uma técnica de uma forma muito simples, usando a combinação de default parameters com uma simples função utilitária que será usada para lançar um erro.
Caso tal atributo não receba um valor em seu parâmetro:
Primeiro criaremos a função utilitária, que será usada como parâmetro default:
function required() {
throw new Error('Atributo não foi informado.');
}
Agora criaremos uma simples função de soma()
, que exige dois parâmetros obrigatórios. Caso contrário, será executada a função required()
, que consequentemente um erro será lançado:
function soma(a = required(), b = required()) {
return a + b;
}
console.log(soma(1, 2)); // 3
console.log(soma(1)); // Uncaught Error: Atributo não foi informado.
Quando não usar arrow functions
Infelizmente, apesar do arrow function ser uma feature muito bacana e útil do ES6, saiba que nem sempre será possível utilizá-la, pois ela não foi feita para substituir completamente a declaração de function principal.
O arrow function, apesar de trazer um syntax sugar e algumas utilidades como, por exemplo, compartilhamento de contextos, você precisa antes de usá-la, aprender também quando não usá-la para não se perder na procura de um bug difícil de resolver.
Veja a seguir uma lista com possíveis situações para não usar arrow function.
Click handlers
Para exemplificar, vamos supor que existe um botão pelo qual queremos mudar sua cor entre azul e vermelho, conforme ele é clicado. Para isso, temos o seguinte html:
<style>
button { background-color: blue; }
.on { background-color: red; }
</style>
<button id="click_here">CLIQUE AQUI!</button>
Agora vamos programar o evento de click desse botão, usando arrow function:
const button = document.querySelector('#click_here');
button.addEventListener('click', () => {
this.classList.toggle('on');
});
Se você testar esse botão, vai gerar o erro “TypeError, cannot read property 'toggle' of undefined
“.
Isso ocorre pois a referência do this
em this.classList.toggle('on')
compartilha os atributos do objeto window do browser, e não do evento. E com isso, nesse caso o ideal é utilizar a declaração de function
:
const button = document.querySelector('#click_here');
button.addEventListener('click', function() {
this.classList.toggle('on');
});
Object methods
Se você pretende criar object methods no front-end, não é recomendado utilizar arrow functions em seus métodos. Entenda o porquê vendo esse exemplo:
const Usuario = {
pontos: 20,
ganhou: () => {
this.pontos++;
}
}
Se você executar Usuario.ganhou()
algumas vezes, a expectativa é de que os pontos estejam com valores acima do que foi estabelecido em seu estado inicial. Ou seja, maior que 20.
Porém, se você acessar Usuario.pontos
, seu valor ainda será 20, e isso ocorre pelo fato de que this
também estará referenciando ao objeto window
do browser, herdando seu escopo.
Para resolver esse problema, você pode usar a declaração clássica de function
ou usar a declaração de métodos do object methods:
// Declarando com function
const Usuario = {
pontos: 20,
ganhou: function() {
this.pontos++;
}
}
// Utilizando Object methods
const Usuario = {
pontos: 20,
ganhou() {
this.pontos++;
}
}
Argumentos de um objeto
Quando se cria uma função que trabalha com arguments
, caso precise tratá-los em uma função interna, também não é recomendado usar arrow function. Veja esse exemplo:
const ordernaPlayers = () => {
const players = Array.from(arguments);
return players.map((player, i) => {
return `${player} chegou em #${i + 1}`;
})
console.log(arguments);
}
O problema, neste exemplo, é que a função ordernaPlayers()
vai gerar o erro: “ReferenceError, arguments is not defined
“. Esse erro ocorrerá pelo simples fato de que arguments
retornará um array, e arrays não utilizam this
– e mesmo não utilizando essa keyword, vai ocorrer esse erro. Para resolver esse bug, basta aplicar function
na declaração da função pai, a ordernaPlayers()
:
const ordernaPlayers = function() {
const players = Array.from(arguments);
return players.map((player, i) => {
return `${player} chegou em #${i + 1}`;
})
console.log(arguments);
}
Prototypes
Jamais use arrow function em funções prototype de um objeto. Simplesmente os atributos não serão definidos, mesmo instanciando o objeto corretamente. Entenda esse exemplo:
class Cliente {
constructor(nome, idade) {
this.nome = nome;
this.idade = idade;
}
}
Cliente.prototype.apresentar = () => {
return `Cliente: ${this.nome} | Idade: ${this.idade}`;
};
Agora tente instanciar um cliente e em seguida, se você invocar a função Cliente.prototype.apresentar()
, a interpolação de seus atributos no resultado final será simplesmente undefined.
const john = new Cliente('John Connor', 25);
const mary = new Cliente('Mary Jane', 20);
john.apresentar(); // 'Cliente: undefined | Idade: undefined'
mary.apresentar(); // 'Cliente: undefined | Idade: undefined'
Simplesmente uma arrow function para métodos de um objeto prototype não mantém estado após instanciado. O ideal, neste caso, é manter a declaração de function normal:
Cliente.prototype.apresentar = function() {
return `Cliente: ${this.nome} | Idade: ${this.idade}`;
};
Múltiplas funções em paralelo
Usando Promises
Caso queira maximizar processamento através da execução paralela de múltiplas funções assíncronas, é possível aplicar esse tipo de processamento usando a combinação de Promises
com async
e await
:
async function parallel() {
const allFuncs = [asyncFunc1(), asyncFunc2()];
const [result1, result2] = await Promise.all(allFuncs);
}
Usando async/await no ES8
Essa dica somente será funcional e aplicável em projetos que já permitem a execução do ES8. Caso contrário, não funcionará em ES7 e, principalmente, em ES6. Basicamente, será aplicada a combinação de async/await
dentro do comando iterador de arrays for...of
.
Ao aplicar essa combinação corretamente, você terá como resultado um loop assíncrono executando em paralelo suas rotinas e, no final, quando todas as iterações assíncronas finalizarem suas execuções, será considerado o final do loop.
Isso é algo muito semelhante a execução de threads simultâneas, existentes em linguagens como: Java, C++, C# e outras. Para aplicar essa combinação execute o código a seguir:
async function processar(lista) {
for await (let i of lista) {
fazerAlgo(i);
}
}
Neste caso, é necessário utilizar a keyword async
na função que possuir o iterador for…of, e com isso utilizar a keyword await entre o for...of
(for await (let i of lista)
), para que permita que cada item da lista tenha um escopo assíncrono em série, onde cada função desse escopo é executada em paralelo, e quando todas terminarem suas rotinas, o loop será finalizado.
Função sleep usando Promises + Async/Await
É possível criar a clássica função sleep()
, combinando Promise
com async
e await
, onde seu objetivo principal é encapsular a função assíncrona setTimeout()
para transformá-la em uma sintaxe síncrona.
Caso não conheça esse tipo de função, basicamente sua funcionalidade é gerar um pause temporário, com base nos milissegundos informados em parâmetro.
Essa função sleep()
deixa o código mais limpo, seguindo o estilo de execução síncrona, mesmo que por de baixo dos panos toda sua execução seja assíncrona, e tudo isso pode ser escrito em uma única linha de código:
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
Para usá-lo é necessário incluir essa função dentro de um escopo que utilize o recurso async
e await
, por exemplo:
async function delaySomething() {
console.log('iniciando delay!');
await sleep(1000);
console.log('finalizando delay!');
}
delaySomething();
// iniciando delay!
// espera 1 segundo
// finalizando delay!
Observação: Todas essas dicas fazem parte do meu recente eBook “JavaScript Awesome Tips (Br version)”, um compilado de dicas e hacks para aplicar em JavaScript, visando código enxuto de alta performance.
Vale a pena conhecer meu eBook para se aprofundar mais, aprendendo desde hacks antigos do ES5, até soluções modernas do ES6, ES7 e ES8.
Link do eBook: https://leanpub.com/javascript-awesome-tips-br-version
Até a próxima, pessoal!