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.