Desenvolvimento

9 jan, 2019

Dicas úteis sobre function do JavaScript

Publicidade

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!