Padrões de concorrência
Não-determinismo
Considere a função chamaDepois(f)
, que chama a função f
após um intervalo de tempo aleatório:
Essa função emula o comportamento de um download ou de um clique do mouse: não podemos prever quando esses eventos acontecerão.
Agora considere que as funções de adicionar e multiplicar números foram implementadas em um computador remoto, e precisamos fazer requisições para esses computadores para realizar as operações. Eis um exemplo prático de programa concorrente no qual a ordem de execução das tarefas afeta o resultado:
Essa solução possui dois problemas:
- não temos como saber a ordem em que as funções serão executadas
- não temos como saber quando as funções terminaram de executar
- estamos chamando
imprime
após 2000 milissegundos, mas quem garante que esse tempo é suficiente?
- estamos chamando
Para resolver isso, usamos callbacks: chamamos a função assíncrona passando uma função que será “chamada de volta” (called back) quando a função assíncrona terminar de executar.
Estrutura sequencial com callbacks
Usamos callbacks para garantir uma ordem sequencial de execução de funções assíncronas:
Qual será a ordem de textos exibidos no console?
À medida que combinamos sequencialmente várias funções assíncronas, o nível de indentação do programa vai aumentando, um problema conhecido como pyramid of doom, callback hell ou mesmo hadouken indentation:
No exemplo abaixo, o que será mostrado no console?
Join
Agora considere a situação em que há duas funções assíncronas, adiciona1
e adiciona2
, que adicionam 1 e 2, respectivamente, à variável x
. Queremos executar as duas funções e, ao final, executar imprime
para ver o resultado.
function adiciona1(callback) {
chamaDepois(function () {
console.log('adiciona');
x = x + 1;
callback();
});
}
function adiciona2(callback) {
chamaDepois(function () {
console.log('adiciona');
x = x + 2;
callback();
});
}
function imprime() {
console.log(x);
}
A diferença aqui é que, dentre adiciona1
e adiciona2
, não importa quem executa primeiro, contanto que imprime
só execute quando as ambas terminarem de executar:
adiciona1 ---
\_____ imprime
/
adiciona2 ---
Esse padrão de concorrência é conhecido como join. Eis uma forma de implementá-lo:
Desafio
Na solução anterior, usamos variáveis globais e colocamos a chamada a imprime
no próprio código das funções adiciona1
e adiciona2
. Tente generalizar essa solução:
- Crie uma função
join(funcoes, callback)
, que recebe um array de funções (funcoes
) e uma função (callback
) - Cada função do array recebe como parâmetro uma função
f
, e chamaf
quando termina de executar - A função
join
chamacallback
quando todas as funções emfuncoes
terminam de executar.
Exercícios
Nesta aula vamos usar o site JSONPlaceholder para dar suporte a nossos exemplos práticos.
Para simplificar o código, definimos a variável global ROOT como sendo a URL do JSONPlaceholder.
As funções $.get(url, f)
e $.post(url, f)
realizam uma requisição AJAX à url
e, quando a resposta chega, chama a função f
passando o objeto associado à resposta.
Para ver o que a chamada $.get(ROOT + '/users', ...)
retorna, basta acessar https://jsonplaceholder.typicode.com/users no seu navegador.
Exemplo simples
Antes de executar o código a seguir, tente prever a ordem de execução das instruções console.log
.
Algo mais elaborado
O exemplo a seguir deveria buscar o id do usuário com username Delphine
e então mostrar o título dos álbuns desse usuário:
No entanto, o programador não percebeu que as chamadas de função que realizou são assíncronas, e por isso o programa não funciona como deveria. Conserte o programa.
Join
Agora você quer pegar o e-mail de todos os usuários pagantes e então exibir os e-mails separados por vírgulas (não importa a ordem). (Suponha que a url /users
, que retorna todos os dados de todos os usuários, não existe). Corrija o código abaixo: