Skip to content

Latest commit

 

History

History
142 lines (95 loc) · 5.53 KB

27.programacao_assincrona_javascript_e_callbacks.md

File metadata and controls

142 lines (95 loc) · 5.53 KB

Programação assíncrona JavaScript e retornos de chamada

Índice

Assincronicidade em Linguagens de programação

Os computadores são assíncronos por design.

Assíncrono significa que as coisas podem acontecer indepentemente do fluxo do programa principal.

Nos computadores de consumo atuais, cada programa é executado por um intervalo de tempo específico e, em seguida, intrompe sua execução para permitir que outro programa continue sua execução. Essa coisa funciona em um ciclo tão rápido que é impossível perceber. Achamos que nossos computadores executam muitos programas simultaneamente, mas isso é uma ilusão (exceto em máquinas multiprocessadoras).

Os programas usam internamente as interrupções, um sinal que é emitido ao processador para chamar a atenção do sistema.

Não vamos entrar em detalhes agora, mas apenas tenha em mente que é normal que os programas sejam assíncronos e interrompam sua execução até que precisem de atenção, permitindo que o computador execute outras coisas nesse meio tempo. Quando um programa está esperando por uma resposta da rede, ele não pode parar o processador até que a solicitação termine.

Normalmente, as linguagens de programação são síncronas e algumas fornecem uma maneira de gerenciar a assincronia na linguagem ou por meio de bibliotecas. C, Java, C#, PHP, Go, Ruby, Swift e Python são todos síncronos por padrão. Alguns deles lidam com operações assíncronas usando threads, gerando um novo processo.

JavaScript

O JavaScript é síncrono por padrão e é de encadeamento único. Isso significa que o código não pode criar novos threads e ser executado em paralelo.

As linhas de código são executados em série, uma após a outra, por exemplo:

const a = 1;
const b = 2;
const c = a * b;
console.log(c);
doSomething();

Mas o JavaScript nasceu dentro do navegador, seu principal trabalho, no início, era responder às ações do usuário, como onClick, onMouseOver, onChange, onSubmit e assim por diante. Como poderia fazer isso com um modelo de programação síncrona?

A resposta estava em seu ambiente. O navegador fornece uma maneira de fazer isso fornecendo um conjunto de APIs que podem lidar com esse tipo de funcionalidade.

Mais recentemente, o Node.js introduziu um ambiente de I/O sem bloqueio para estender esse conceito de acesso a arquivos, chamadas de redes e assim por diante.

Callbacks

Você não pode saber quando um usuário vai clicar em um botão. Assim, você define um manipulador de eventos para o evento de clique. Este manipulador de eventos aceita uma função, que será chamada quando o evento for adicionado:

document.getElementById('button').addEventListener('click', () => {
  // item clicado
});

Este é o chamado callback.

Um callback é uma função simples que é passado como valor para outra função e só será executada quando o evento acontecer. Podemos fazer isso porque o JavaScript tem funções de primeira classe, que podem ser atribuídas a variáveis e passadas para outras funções (chamadas funções de ordem superior).

É comum envolver todo o código do seu cliente em um evento chamado load no objeto window, que executa a função de retorno de chamada somente quando a página está pronta:

window.addEventListener('load', () => {
  // janela carregada
  // O que você quer
});

Callbacks são usados em todos os lugares, não apenas em eventos DOM.

Um exemplo comum é usados temporizadores:

setTimeout(() => {
  // executa depois de 2 segundos
}, 2000);

As solicitações XHR também aceitam um callback, neste exemplo, atribuindo uma função a uma propriedade que será chamado quando um determinado evento ocorrer (neste caso, o estado da solicitação muda):

const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
  if (xhr.readyState === 4) {
    xhr.status === 200 ? console.log(xhr.responseText) : console.error('error');
  }
};
xhr.open('GET', 'https://yoursite.com');
xhr.send();

Lidando com erros em callbacks

Como você lida com erros com callbacks? Uma estratégia muito comum é usar o que o Node.js adotou: o primeiro parâmetro em qualquer função de callback é o objeto error: error-first callbacks.

Se não houver erro, o objeto é null. Se houver um erro, ele contém alguma descrição do erro e outras informações.

const fs = require('fs');

fs.readFile('/file.json', (err, data) => {
  if (err) {
    // manipulador de erros
    console.log(err);
    return;
  }

  // sem erros, processa os dados
  console.log(data);
});

O problema com os callbacks

Os callbacks são ótimos para casos simples!

No entanto, cada callback adiciona um nível de aninhamento e, quando você tem muitos callbacks, o código começa a ficar complicado muito rapidamente:

window.addEventListener('load', () => {
  document.getElementById('button').addEventListener('click', () => {
    setTimeout(() => {
      items.forEach(item => {
        // seu código aqui
      });
    }, 2000);
  });
});

Este é apenas um código simples de 4 níveis, mas já vi muitos mais níveis de aninhamento e não é divertido.

Como solucionamos isso?

Alternativas para callbacks

A partir do ES6, o JavaScript introduziu vários recursos que nos ajudam com código assíncrono que não envolve o uso de callbacks: Promises (ES6) e Async/Awit (ES2017).