O Javascript tem como uma de suas características mais marcantes o fato de ser assíncrono, ou seja, quando uma chamada a uma função que não irá retornar imediatamente seu valor é realizada, o interpretador simplesmente pula para a próxima linha de código, por exemplo:

1
2
3
4
5
6
7
8
function assincrono(params) {
setTimeout(function() {
console.log(params.txt);
}, params.milis);
}
assincrono({txt:'primeiro', milis:1000});
console.log('segundo');

Irá retornar:

segundo

primeiro

Até a versão ES5 do Javascript, caso fosse implementanda uma rotina onde é absolutamente necessário aguardar o retorno de uma chamada para executar a próxima, era necessário fazer o uso de callbacks:

1
2
3
4
5
6
7
8
9
10
function assincrono(params, callback) {
setTimeout(function() {
console.log(params.txt);
callback();
}, params.milis);
}
assincrono({txt:'primeiro', milis:1000}, function() {
console.log('segundo');
});

Nesse caso o retorno seria o esperado:

primeiro

segundo

Entretando, dependendo da complexidade da rotina, pode ser necessário aguardar mais de uma vez:

1
2
3
4
5
6
7
8
9
umaFuncaoAssincrona(params, function() {
eOutra(params, function() {
eMaisOutra(params, function() {
eAssimPorDiante(params, function(resultadoFinal){
console.log(resultadoFinal);
});
});
});
});

Não há necessariamente algo errado com essa implementação, porém a legibilidade do código torna-se quase um trabalho árduo quando a lógica de cada chamada começa a aumentar em lnhas de código. Com isso em mente, na versão ES6 do Javascript foram instroduzidas as promises.

Basicamente, uma promise é uma função que retorna um valor “provisório” até que o valor real seja retornado pela rotina, por exemplo:

1
2
3
4
5
6
7
8
9
10
11
12
function assincrono(params) {
return new Promise(function(resolve) {
setTimeout(function() {
console.log(params.txt);
resolve();
}, params.milis);
});
}
var r1 = assincrono({txt: 'primeiro', milis:1000});
var r2 = r1.then(assincrono.bind(null, {txt: 'segundo', milis: 1200}));
var r3 = r2.then(assincrono.bind(null, {txt: 'terceiro', milis: 400}));

Nesse caso o resultado segue exatamente a ordem especificada:

primeiro

segundo

terceiro

O que deve ser entendido nesse caso é que uma promisse possui um método then() que é chamado no momento em que a promise tem seu valor resolvido. O then() recebe por parâmetro quaisquer valores passados como parâmetro na função resolve() da promise. Uma vez que o then() retorna uma nova promise, podemos facilmente aninhar nossas chamadas. No exemplo acima podemos observar:

  • Temos a variavel r1 contendo a promise retornada pela função assincrono.
  • Em seguida, usamos o método bind() para retornar uma nova variante da função assincrono (mais sobre isso adiante) e a colocamos como chamada para o then da promise r1. Essa nova promise gerada pelo then é armazenada na variável r2
  • Para a variável r3 basicamente repetimos o mesmo processo acima

A partir do ES5, o Javascript adicionou o método bind ao protótipo das funções. O que ele faz é basicamente sobrescrever a referência do this dentro do contexto da função, passando-o por parâmetro (o primeiro). Uma coisa importante sobre o bind é que ele retorna uma nova função, mas não a executa. Por exemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// uma "classe" simples para representar um botão
var Botao = function(texto) {
this.texto = texto;
}
// adicionamos o metodo clicar ao botao, fazendo referencia ao atributo texto da instancia
Botao.prototype.clicar = function() {
console.log(this.texto + ' foi clicado');
}
// criamos uma nova instancia do botao
var botaoOk = new Botao('OK');
// chamamos diretamente o método
botaoOk.clicar();
// funcao com callback simples
var executar = function(callback) {
callback();
};
// dizemos que o callback é o metodo clicar
executar(botaoOk.clicar);

O resultado é:

OK foi clicado

undefined foi clicado

Como o método clicar() está dentro de outro contexto, o valor do this na classe passa a ser diferente do original. Para evitar esse problema, pode-se simplesmente usar o bind:

1
executar(botaoOk.clicar.bind(botaoOk));

Como falado, o primeiro parâmetro d bind() sempre é o novo valor do this. Porém, parâmetros adicionais também podem ser passados. No nosso caso, o bind necessitava de parâmetros pré-definidos, porém não deveria ser executado pois era necessário que fosse passado como parâmetro para o then.

Portanto, para que a função fosse executada com os parâmetros corretos, porém só fosse executada após o then da promise anterior, usamos o bind para gerar uma nova função a ser passada por referência. Com isso, chegamos em nossa primeira solução para escapar do callback hell.

Uma segunda característica interessante do ES6 são os generators. Eles basicamente atuam como interruptores, onde nós decidimos quando a execução deve pausar e quando continuar. Eis um exemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function assincrono(params) {
return new Promise(function(resolve) {
setTimeout(function() {
resolve(params.txt);
gen.next(params.txt);
}, params.milis);
});
}
function* sequencia() {
var p1 = yield assincrono({txt:'primeiro ',milis:1500});
var p2 = yield assincrono({txt:'segundo ',milis:1200});
var p3 = yield assincrono({txt:'terceiro',milis:1000});
var p4 = yield assincrono({txt:'quarto',milis:1100});
console.log(p1,p2,p3,p4);
};
var gen = sequencia();
gen.next();

Segue a explicação:

  • Nossa função assincrono() continua igual, exceto que agora ela possui uma chamada para o método next() do generator.
  • O generator é caracterizado pelo asterisco após a palavra function.
  • O comando yield pausa a execução até que o método next() seja chamado.
  • é necessário instanciar nosso generator (especialmente porque ele é referenciado na funçao assíncrona) e dar o primeiro next() para iniciá-lo.

Como estamos sempre chamando o next() após a conclusão da chamda assíncrona, o resultado final fica:

primeiro

segundo

terceiro

quarto

Comentar e compartilhar

Uma coisa bastante útil que descobri é que é possível obter o par chave-valor dentro do ngRepeat. Isso torna as coisas mais interessantes, aumentando as possibilidades do que pode ser feito com a diretiva.

Supondo que eu deseje gerar uma tabela com um número dinâmico tanto de linhas, quanto de colunas e partindo do princípio de que eu tenho os dados no seguinte formato e gostaria de obter o resultado abaixo:

1
2
3
4
5
$scope.dados = [
{col_1: 'aaa', col_2: 'bbb', col_3: 'ccc'},
{col_1: 'ddd', col_2: 'eee', col_3: 'fff'},
{col_1: 'ggg', col_2: 'hhh', col_3: 'iii'}
];
Coluna 1 Coluna 2 Coluna 3 Coluna N
aaa bbb ccc
ddd eee fff
ggg hhh iii zzz

Para obter o nome da chave é simples assim:

1
2
3
<tr ng-repeat="linha in dados">
<td ng-repeat="(chave, valor) in linha">{{ valor }}</td>
</tr>

Caso eu deseje dar um nome mais humanizado às colunas, posso adicionar um novo objeto de mapeamento:

1
2
3
4
5
$scope.colunas = {
col_1: 'Coluna 1',
col_2: 'Coluna 2',
col_3: 'Coluna 3'
};

E finalmente, o resultado final:

1
2
3
4
5
6
7
8
9
10
11
12
<table>
<thead>
<tr>
<th ng-repeat="col in colunas">{{ col }}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="linha in dados">
<td ng-repeat="(chave, valor) in linha">{{ valor }}</td>
</tr>
</tbody>
</table>

Plunkr: https://plnkr.co/edit/9wLTqgNUB7qG8qA499Hx?p=preview

Comentar e compartilhar

Nem sempre podemos nos dar ao luxo de ter uma IDE à mão naquele momento em que precisamos corrigir algum erro ou mesmo testar nosso código. Além disso, várias IDEs adicionam uma carga extra na hora de debuggar uma aplicação, tornando a execução em modo debug mais lenta.

Qualquer que seja o caso, se você estiver sem uma IDE, apenas quiser economizar tempo ou então simplesmente quiser aprender uma forma diferente, eis o momento.

Para começar é bastante simples. Basta rodar o seguinte comando no console:
node --debug index.js

Ou, se preferir que o debugger pare imediatamente na primeira linha do seu script, o comando muda um pouco:
node --debug-brk index.js

A partir daí, você pode adicionar breakpoints no seu código desta forma:

1
2
3
4
var teste = function (a, b) {
debugger;
return a + b;
};

A instrução debugger sinaliza que naquela linha a execução deverá ser interrompida e o contexto analizado. Entretanto, é necessário reiniciar seu script para que o breakpoint tenha efeito. Isso é feito no console através do comando restart.

Para controlar a execução, basta usar os seguintes comandos no console:

  • cont ou c: continua a execução (equivalente ao F8 no Chrome DevTools)
  • pause: pausa a execução (F8 no DevTools)
  • next ou n: avança para a próxima linha (F10 no DevTools)
  • step ou s: “entra” em uma função (F11 no DevTools)
  • out ou o: “sai” de uma função (Shift + F11 no DevTools)

Se preferir adicionar breakpoints sem ter que alterar seu script ou reiniciar sua aplicação, você pode seguir o seguinte:

  • sb(): define um breakpoint na linha atual
  • sb(8): define um breakpoint na linha 8 do script atual
  • sb('minhaFuncao()'): define um breakpoint na primeira linha da função informada
  • sb('outro-script.js', 2): define um breakpoint na linha 2 do script informado
  • cb(...): remove um breakpoint (aceita os mesmos parâmetros do comando sb()

Também é possível observar valores com o comando watch, por exemplo:
watch(JSON.stringify(meuObjeto))

A qualquer momento você pode consultar o valor de seus watchs com o comando watchers. Para remover um watch, o comando é unwatch(...).

Mais alguns comandos úteis:

  • list(10): mostra 10 linhas de código antes e depois da linha atual
  • exec JSON.stringify(meuObjeto): executa uma expressão no contexto atual
  • repl: abre um “shell” do node no contexto atual onde você pode editar o código em tempo de execução (entretanto ele não será salvo). Para sair do shell, basta teclar CTRL + C

Comentar e compartilhar

Quando estou desenvolvendo em JS às vezes sinto falta do Python e seus operadores malandros, do tipo:

1
2
3
4
meuArray = ['a','b','c','d']
print meuArray[2:]
print meuArray[:3]
print meuArray[1:2]

O primeiro print irá retornar: ['c', 'd']
O segundo irá retornar: ['a', 'b', 'c']
O último irá retornar: ['b']

Como não temos algo assim em JS e nem nativo no Angular, é necessário criar um filter, mais ou menos assim:

1
2
3
4
5
6
7
8
9
app.filter('slice', function() {
return function(arr, inicio, fim) {
inicio = inicio ? inicio : 0;
if (fim) {
return arr.slice(inicio, fim);
}
return arr.slice(inicio);
};
});

Para utilizá-lo:

1
2
3
<ul>
<li ng-repeat="item in meuArray | slice:1:2">{{item}}</li>
</ul>

Fiddle: https://jsfiddle.net/6zvcd6vz/2/

Comentar e compartilhar

  • página 1 de 1

Brutto-AVT

Blog para manter dicas e anotações relacionadas a web dev.


Web developer @ Virtual Software para Seguros


Brazil