Síncrono e Assíncrono - sabrinabm94/javascript GitHub Wiki

12 - Síncrono e Assíncrono em JavaScript

Quando falamos sobre programação em JavaScript, é crucial entender a diferença entre código síncrono e assíncrono. Vamos abordar esses conceitos e explorar exemplos para uma compreensão mais clara neste capítulo.

12.1 Síncrono (Sync)

O termo "síncrono" refere-se a uma função que pausa a execução para realizar uma tarefa, continuando apenas após a conclusão dessa tarefa. Em outras palavras, o código síncrono é executado em uma sequência linear, uma linha de cada vez.

function syncFunction(string) {
  return string;
}

const result = syncFunction('Hello world!');
console.log(result); // Retorna: Hello world!

No exemplo acima, a função syncFunction é síncrona. Ela executa imediatamente e retorna o texto passado como argumento. Aqui, o texto 'Olá mundo!' é passado como argumento, e o resultado é impresso no console.

12.2 Assíncrono (Async)

As funções assíncronas são aquelas que não bloqueiam a execução do código enquanto esperam por uma tarefa ser concluída. Em vez disso, elas continuam a executar outras operações enquanto aguardam o resultado da tarefa assíncrona.

async function asyncFunction(string) {
  // Simulando um tempo de espera de 1 segundo
  await new Promise(resolve => setTimeout(resolve, 1000));
  return string;
}

// Chamada da função assíncrona
asyncFunction('Hello world!').then(result => {
  console.log(result); // Saída após aproximadamente 1 segundo: Hello world!
});

Neste exemplo, a função asyncFunction é assíncrona. Ela aguarda um segundo (simulado com setTimeout) antes de retornar o texto 'Olá mundo!'. A palavra-chave async antes da definição da função indica que ela é assíncrona. A chamada da função é feita usando .then() para lidar com o resultado após a espera.

12.2.1 Callbacks

Callbacks são funções que são passadas como argumentos para outras funções e executadas após a conclusão de uma determinada operação. Elas são comumente usadas em JavaScript para lidar com operações assíncronas.

// Função assíncrona que retorna um texto após um certo tempo
function asyncFunctionHell(callback) {
  // Simulando um tempo de espera de 1 segundo
  setTimeout(() => {
    callback('Hello');
    setTimeout(() => {
      callback('world');
      setTimeout(() => {
        callback('!');
      }, 1000);
    }, 1000);
  }, 1000);
}

// Chamada da função com callback hell
asyncFunctionHell(firstText => {
  console.log(firstText); // Saída após aproximadamente 1 segundo: Hello
  asyncFunctionHell(secondText => {
    console.log(secondText); // Saída após aproximadamente 2 segundos: world
  });
  asyncFunctionHell(thirdText => {
    console.log(thirdText); // Saída após aproximadamente 3 segundos: !
  });
});

Neste exemplo, a função asyncFunctionHell utiliza três callbacks aninhados para lidar com a execução assíncrona. Quando a primeira parte do texto é retornada após 1 segundo, a segunda parte é retornada após mais 1 segundo e a terceira em mais um segundo, totalizando 3 segundos de execução do encadeamento, criando uma estrutura de callback hell.

12.2.2 Promise

As Promises são objetos que representam o resultado de uma operação assíncrona. Elas podem estar em um dos três estados: pendente, realizada (resolved) ou rejeitada (rejected). As Promises são úteis para lidar com código assíncrono de forma mais organizada.

// Função assíncrona que retorna um texto
function asyncFunctionNew(text) {
  return new Promise((resolve, reject) => {
    resolve(text);
  });
}

// Chamada da função com Promise
asyncFunctionNew('Hello')
  .then(firstText => {
    console.log(firstText); // Saída: Hello
    // Retorna a segunda parte
    return asyncFunctionNew('world!');
  })
  .then(secondText => {
    console.log(secondText); // Saída: world!
  });

Neste exemplo, asyncFunctionNew retorna uma Promise que é resolvida com o texto 'Hello'. Usamos .then() para lidar com o resultado da Promise e encadear operações assíncronas de forma clara e concisa. Isso evita o "callback hell" e torna o código mais legível.

12.2.2.1 Await

O await é uma palavra-chave usada dentro de funções async em JavaScript para aguardar a conclusão de uma operação assíncrona. Quando utilizada com uma Promise, ela pausa a execução do código até que a Promise seja resolvida, permitindo que você trabalhe com o resultado como se fosse síncrono.

const jolteon = await fetch('https://pokeapi.co/api/v2/pokemon/135')
  .then(response => response.json());

console.log(jolteon.name);

O await é útil para garantir que certas operações sejam executadas de forma sequencial, evitando o paralelismo e tornando o código mais fácil de entender.

12.2.2.2 Promise.all

Promise.all é um método usado para executar várias Promises em par

alelo e aguardar que todas elas sejam resolvidas.

Promise.all([
  new Promise(resolve => setTimeout(resolve, 1500)),
  new Promise(resolve => setTimeout(resolve, 900)),
  new Promise(resolve => setTimeout(resolve, 2200))
])
.then(results => results.length.b.c)
.then(c => console.info(c))
.catch(err => console.error(err))

O Promise.all é útil quando você precisa executar várias operações assíncronas simultaneamente e só deseja continuar quando todas forem concluídas com sucesso.

12.2.2.3 Promise.allSettled

Promise.allSettled é semelhante ao Promise.all, mas em vez de interromper se uma Promise for rejeitada, ele aguarda que todas as Promises sejam concluídas.

const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.allSettled(promises);
const successfulPromises = results.filter(p => p.status === 'fulfilled');

Promise.allSettled é útil quando você precisa executar várias operações assíncronas e deseja lidar com o resultado de todas, independentemente de serem bem-sucedidas ou falharem.

12.2.2.4 Promise.race

Promise.race é um método usado para executar várias Promises em paralelo e aguardar a primeira delas ser resolvida ou rejeitada.

const p1 = Promise.race([
  new Promise(resolve => setTimeout(resolve, 4000)),
  new Promise((resolve, reject) => setTimeout(reject, 8000))
])

const p2 = Promise.race([
  p1,
  new Promise(resolve => setTimeout(resolve, 6000)),
  new Promise(resolve => setTimeout(resolve, 10000)),
  new Promise((resolve, reject) => setTimeout(reject, 2000))
])

p2.then(result => console.log(result))
p2.catch(err => console.error(err))

Promise.race é útil quando você deseja lidar com a resposta da primeira operação assíncrona a ser concluída, seja ela um sucesso ou uma falha.

12.2.3 Fetch

O fetch é uma função incorporada do JavaScript usada para fazer requisições HTTP para servidores web. Ela retorna uma Promise que resolve a resposta da requisição HTTP.

try {
  const firstPokemon = fetch('https://pokeapi.co/api/v2/pokemon/1/');

  firstPokemon
    .then(response => {
      // Verifica se a resposta não é bem-sucedida
      if (!response.ok) {
        throw new Error('Erro ao carregar os dados do Pokémon');
      }
      return response.json();
    })
    .then(response => {
      console.log(response);
    })
    .catch(error => {
      console.error('Ocorreu um erro:', error);
    });
} catch (error) {
  console.error('Ocorreu um erro ao executar a requisição:', error);
}

Neste código, o bloco try envolve todo o código que faz a requisição usando o fetch. Dentro do primeiro .then(), verificamos se a resposta não é bem-sucedida usando response.ok. Se não for, lançamos um erro com throw new Error('Mensagem de erro'). O .catch() no final captura qualquer erro que ocorra durante o processo de requisição ou manipulação dos dados.

12.2.4 Observable

Observable é uma técnica utilizada para compartilhar dados, podendo ser vista como uma evolução das promises. Os observables têm três callbacks principais: complete, next e error. Isso significa que podemos realizar ações quando o fluxo de dados é concluído, quando um novo valor é emitido ou quando ocorre um erro.

oneFunction = new Observable((observer) => {
    observer.next('1');
    observer.next('2');
		observer.error('ERRO!');
  });

this.oneFunction.subscribe();
this.oneFunction.unsubscribe(); // Lembre-se sempre de cancelar a inscrição no observable após o uso, para evitar problemas de desempenho, por exemplo, ao destruir o componente ou serviço.

Os observables oferecem uma maneira poderosa de lidar com fluxos de dados em tempo real em aplicações web, proporcionando uma maior flexibilidade e controle sobre os dados que estamos manipulando.

Ao escolher a melhor abordagem para lidar com funções assíncronas no JavaScript moderno, é preciso levar em consideração tanto a performance quanto os princípios de código claro (clear code) e boas práticas de desenvolvimento. As promises, especialmente em conjunto com o await e o fetch, tendem a ser a escolha mais eficiente e legível na maioria dos casos.

As promises oferecem um modelo mais organizado e flexível para lidar com operações assíncronas, permitindo encadear várias chamadas de forma clara e concisa. O await complementa as promises, tornando o código ainda mais legível ao aguardar a conclusão de uma operação assíncrona antes de prosseguir.

O fetch, por sua vez, é uma abordagem moderna e poderosa para fazer requisições HTTP em JavaScript, integrando-se perfeitamente com as promises e oferecendo uma maneira simples e intuitiva de interagir com APIs de servidores.

Em resumo, ao escolher a melhor abordagem para funções assíncronas em JavaScript, é importante considerar a clareza do código, a eficiência de desempenho e a adequação ao contexto específico de cada aplicação. As promises, o await, o fetch e os métodos de Promise são geralmente as escolhas mais recomendadas para garantir um código claro, legível e eficiente.