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?

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 chama f quando termina de executar
  • A função join chama callback quando todas as funções em funcoes 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: