Programação Síncrona e Assíncrona

Introdução a Ideia

Vamos supor que você possui uma lista de tarefas domésticas, onde você precisa:

  • Limpar a Cozinha,
  • Aspirar seu Quarto,
  • Lavar o Banheiro,
  • Fazer o Almoço,
  • Buscar seu filho na escola.

Considerando que você é um tipo de pessoa metódica, você acabou por decidir realizar todas as tarefas na ordem em que elas foram descritas.

Pois bem, você decidiu começar limpando a cozinha, e sendo assim, você pegou a vassoura para varrer o chão, em seguida lavou a louça e guardou as panelas.

Legal, primeira tarefa foi concluída com sucesso 🥳

Com a primeira tarefa riscada da lista, você optou por aspirar seu quarto. Com isso você pegou o aspirador de pó, conectou na tomada e deu início ao processo.

Show! Segunda tarefa concluída 😎

Chegou a hora de lavar o banheiro! E você acabou percebendo que faltava sabão em pó, como você não estava afim de sair de casa, e por comodidade própria, você decidiu pedir no mercado que te entregassem o sabão em pó.

E como você é metódico demais e gosta de concluir as tarefas passo a passo, você esperou 30 minutos.... 1 hora.... 1 hora e meia... 2 horas... 3 horas... 

Quando de repente a campainha toca e você vai buscar o sabão em pó para continuar a lavagem do banheiro.

Porém, o almoço ainda não saiu, e os professores estão presos no trabalho por conta que você ainda não foi buscar seu filho na escola 🤣

Você não só se atrasou como atrasou a vida das outras pessoas também.

Mal sabe eles que você ainda vai preparar o almoço para depois ir a escola buscar o seu filho 😱

No exemplo acima, você pode ser considerada um tipo de pessoa síncrona, que só consegue fazer as coisas em sequência, uma após a outra.

Agora... se você fosse um tipo de pessoa, digamos que mais... assíncrono, a partir do momento que pedisse para o mercado te entregar o sabão em pó, você daria continuidade as outras tarefas, enquanto espera a sua entrega.

"Ok, enquanto o sabão em pó não chega, vou aproveitar para fazer o almoço e buscar o meu filho na escola".

Agora que você aprendeu a essência dos termos assíncrono e síncrono, chegou o momento de entendermos como esses dois conceitos se aplicam no mundo do JS.

Programação Síncrona

Até o presente momento, a maioria dos códigos que vimos em conteúdos anteriores, sempre funcionaram de forma síncrona, ou seja, a execução do código sempre ocorreu em sequência, uma instrução após a outra.

console.log('Eu Serei executado primeiro 🤩');

console.log('Segunda lugar!!! 😞');

console.log('Acho que sou o último né 😭');

O mesmo vale para as funções:

function soma(num1, num2) {
return num1 + num2;
}

console.log('Um calculo será realizado...');

console.log('2 mais 2 é: ' + soma(2, 2)) // 4

console.log('Fim do programa!');

Isso significa, que precisamos aguardar o JS executar uma linha, para que a próxima seja executada.

E isso não é errado, ok? Durante alguns momentos (se não na maior parte do tempo) precisamos sim fazer o uso da programação síncrona, pois se não nosso sistema ficará uma loucura.

Até aí, tudo bem!

Por um outro lado, no dia a dia do desenvolvimento web, estamos consumindo cada vez mais dados externos, principalmente quando estamos trabalhando com desenvolvimento front-end.

No conteúdo que fala sobre Requisições, você aprendeu que o JS pode fazer o uso das bibliotecas XMLHttpRequest e API Fetch para enviar e recuperar dados e informações.

Durante os seus testes, talvez as suas requisições tenham sido executas instantaneamente, de modo que deu a impressão de que todo o seu código foi executado de forma síncrona.

O que não é verdade, pois no caso da biblioteca API Fetch usamos meios assíncronos por de baixo dos panos, e isso tudo é feito com o uso do método then().

Then significa então/depois/em seguida, que nada mais é do que um advérbio usado após a conclusão de uma tarefa anterior.

  • Lave seu carro, e então coloque-o na garagem.
  • Vou jantar e em seguida escovar meus dentes.

Já no caso do XMLHttpRequest, precisamos definir um terceiro parâmetro como true, indicando que a resposta deve ser tratada assincronamente.

var xhr = new XMLHttpRequest();
xhr.open("GET", "https://meusite.com/produtos", true);//Quando 'true' é usado como 3° parâmetro da função, a biblioteca se encarrega de tratar a resposta assincronamente. 
....

Já no caso da API Fetch, o then() já funciona de forma assíncrona por padrão.

Programação Assíncrona

Esse tipo de programação, veio para responder a seguinte pergunta: "E se por acaso sua conexão de internet estiver mais lenta, ou o servidor estiver tão atarefado com outras requisições, de modo que a resposta seja demorada? O código vai travar até que o servidor responsa? E como fica a experiência do usuário em relação a isso?".

Imagina o seu sistema operacional congelado até que um download seja concluído? Realmente não dá né...

Bem, aí nesse caso precisamos da programação assíncrona.

Chamamos de programação assíncrona o ato de executar uma tarefa em "segundo plano", sem nosso controle direto disso.

Isso significa dizer que o Javascript vai separar seu código em duas partes:

  • Processo que rodam agora,
  • Processos que vão rodar depois de algo acontecer.

No âmbito dos navegadores de internet (famosos browsers), temos o conceito de event-driven, que significa orientado a eventos.

Ou seja, eventos que só acontecem quando outros eventos são chamados, e isso incluí:

  • Quando declaramos uma função que só irá ser executada quando o usuário clicar em um botão.
  • Comandos que só serão executados caso o usuário digite algo no campo de texto.
  • Quando o cursor do mouse passa acima de algum elemento do HTML 5, e uma função é executada.

Pense nas armadilhas de caça, que só executam seu propósito quando algum animal "ativa" elas.

Aplicações como Whatsapp, Telegram, Facebook e entre outras, são exemplos de comunicação assíncrona, onde NÃO precisamos ficar olhando para a tela esperando uma outra pessoa responder (ou pelo menos não deveríamos, pois não sabemos quando a resposta vai chegar 😅).

Promises

Para que o processamento assíncrono exista no mundo do Javascript, foi-se necessário que existissem as promises.

Promises, que significa promessas, como o próprio nome já nos diz, são comandos que só serão executados quando um outro evento acontecer.

Exemplo disso, é quando alguém manda uma mensagem no whatsapp para você esperando uma resposta sua, prometendo que você vai responder. Tal resposta que pode vir na hora (caso você estiver online), daqui a um tempo, ou quem sabe nunca rs

Quando estamos trabalhando com Requisições, temos uma promessa de que esses dados irão chegar, entretanto, enquanto esses dados não chegam, nosso sistema precisa continuar rodando.

No universo do JS, existem algumas formas de se trabalhar com processamento assíncrono, e isso pode ser feito usando alguns comandos e métodos como: then()async/await e o uso do objeto Promise().

CallBacks

A verdade é que podem existir somente callbacks, mas não existem promises sem callbacks.

Antes de mais nada, precisamos entender o que é um callback.

Callbacks são funções que são passadas dentro dos argumentos de outras funções.

Este procedimento é válido em JavaScript porque funções são objetos e objetos podem ser passados como argumentos para funções.

A estrutura básica da função callback é a seguinte:

function minhaFuncao(callback){
callback();
}

Exemplo de callback simples em ação:

function mostraNome(){
console.log('Olá Micilini');
}

function minhaFuncao(mostraNome){
mostraNome();
}

minhaFuncao(mostraNome);

As funções de retorno de chamada (callbacks) são usadas para adquirir controle sobre a sequência de execução.

Geralmente, as funções são executadas com base na sequência em que são invocadas, não na sequência em que são definidas. Por exemplo:

function trabalhoUm() {
console.log("Um");
}
function trabalhoTres() {
console.log("Dois");
}
function trabalhoDois() {
console.log("Tres");
}


trabalhoUm();
trabalhoDois();
trabalhoTres();

Mas e se quisermos invocar a tarefa 2 somente quando a tarefa 1 terminar seu processamento?

Para isso, precisamos de funções de retorno de chamada, e algum conhecimento básico de execução assíncrona.

Antes de nos aprofundarmos no conceito dos callbacks, precisamos conhecer duas funções do próprio JS, que são responsáveis pelo controle da execução do código.

SetTimeOut

O método setTimeOut() executa uma função após alguns milisegundos.

No caso desse método, 1 segundo equivale a 1000 milisegundos.

const timeOut = setTimeout(mostraMensagem, 5000);

function mostraMensagem() {
console.log('Olá Mundo');
}

A mensagem acima será mostrada após 5 segundos, logo após o código ser executado.

O método pode ser executado sem ser atribuído a uma variável:

setTimeout(mostraMensagem, 5000);

Você também pode declarar esse método em conjunto com funções anônimas:

setTimeout(function(){
console.log('Anônima foi executada');
}, 5000);

SetInterval

O método setInterval() chama uma função em intervalos especificados (em milissegundos).

No caso desse método, 1 segundo equivale a 1000 milisegundos.

const interval = setInterval(mostraMensagem, 1000);

function mostraMensagem() {
console.log('Olá Mundo');
}

O intervalo termina sempre quando chamamos o comando clearInterval() ou fechamos a janela do navegador.

const interval = setInterval(mostraMensagem, 1000);

function mostraMensagem() {
console.log('Olá Mundo');
clearInterval(interval);
}

Observe que estamos passando o nome da variável que armazena o método setinterval.

Se este método estivesse solto no código, não conseguiríamos chamar o clearInterval().

Lembrando que podemos usar o setInterval de forma solta (sem atrelar a uma variável) ou por meio de funções anônimas.

Simulando Requisições em CallBacks

Agora que você já tem o conhecimento sobre as duas funções de tempo (setTimeOut e setInterval), podemos continuar no aprofundamento dos conceitos de callback.

Observe o código abaixo:

var nomes = ["Micilini", "Joana", "Allison"];

function addNome(nome) {
setTimeout(function () {
nomes.push(nome);
console.log("Nome Adicionado: " + nome);
}, 200);
}

function mostrarNomes() {
setTimeout(function () {
console.log("Nomes na Lista", nomes);
}, 100);
}

addNome("Carla");
mostrarNomes();

Apesar da função addNome ser chamada primeiro, ela foi executada depois, isso por conta do setTimeOut.

Isso ocorre porque o programa não esperou que a função addNome() terminasse o processamento.

Agora, se usarmos funções de retorno de chamada para executar a função mostraNomes() somente após o término de addNome(), nosso código ficaria dessa forma:

var nomes = ["Micilini", "Joana", "Allison"];

function addNome(nome, callback) {
setTimeout(function () {
nomes.push(nome);
console.log("Nome Adicionado: " + nome);
callback();
}, 200);
}

function mostrarNomes() {
setTimeout(function () {
console.log("Nomes na Lista", nomes);
}, 100);
}

addNome("Carla", mostrarNomes);

Then()

O comando then() foi usado em conteúdos anteriores com a biblioteca API Fetch, e ele por si só é considerado uma promise que vai resolver a requisição, tendo ou não sucesso.

É importante ressaltar que o then() não é um comando exclusivo pertencente a biblioteca da API Fetch, visto que podemos usa-lo em conjunto com Promises.

function pegarUsuario(idUsuario) {
const userData = fetch(`https://meusite.com/usuario/${idUsuario}`)
.then(response => response.json())
.then(data => console.log(data.name))
}

pegarUsuario(1);//Retorna o nome do usuário

No caso do comando then() ele promete retornar um objeto.

Também podemos fazer o uso do comando finally() para executar um bloco de código no final de uma promessa:

function pegarUsuario(idUsuario) {
const userData = fetch(`https://meusite.com/usuario/${idUsuario}`)
.then(response => response.json())
.then(data => console.log(data.name))
.catch(error => console.log(error))
.finally(() => { console.log('A promessa foi finalizada!'); })
}


pegarUsuario(1);//Retorna o nome do usuário

É importante ressaltar que o comando finally é chamado independente de sucesso ou falha da promessa, e a função callback deste método é sempre executada por último.

Promise()

Promise() é uma classe que permite construir funções de processamento de dados assíncrono representando um valor que poderá estar disponível no futuro.

A sua sintaxe é:

new Promise((resolve: Function, reject: Function) => void)

resolve: é a função responsável por retornar o resultado da promessa.

reject: é a função responsável por retornar o erro da promessa.

Para que possamos entender como funciona o objeto promise, vejamos um exemplo simples:

const valor = 15;

function verificaMaiorQueVinte(){

return new Promise((resolve, reject) => {
valor > 20 ? resolve('Valor é maior que 20...') : reject('Valor é menor que 20...');
}).then((retorno) =>{
console.log('Sucesso: ' + retorno);
}).catch((retorno) => {
console.log('Erro: ' + retorno);
}).finally((retorno) => {
console.log('Completo!');
});

}

verificaMaiorQueVinte();

Vamos agora analisar cada parte do código cima:

1) Foi declarado uma constante que armazena o valor 15;

2) Foi declarado uma função chamada verificaMaiorQueVinte, que tem por objetivo retornar uma promessa.

3) Estamos chamando a função verificaMaiorQueVinte, que tem por objetivo executar uma promisse que inicialmente checa se o valor existente dentro da variável valor é maior que 20.

3.1) Caso o valor for maior que 20, ele executa a função resolve, que foi passada por parâmetro dentro do objeto promise. Essa função pode haver parâmetros dentro dela, mas isso é opcional.

3.1.1) Após a execução da função resolve, automaticamente o JS irá executar o bloco do then(), que por sua vez mostrará a seguinte mensagem no console 'Sucesso: Valor é maior que 20...'.

3.2) Caso o valor for menor que 20, ele executa a função reject, que foi passada por parâmetro dentro do objeto promise. Essa função pode haver parâmetros dentro dela, mas isso é opcional.

3.2.1) Após a execução da função reject, automaticamente o JS irá executar o bloco do catch(), que por sua vez mostrará a seguinte mensagem no console "Erro: Valor é menor que 20...".

4) Após o then() ou o catch(), o objeto promise sempre irá executar tudo o que estiver dentro do bloco finally(), nesse caso o JS mostrará a seguinte mensagem final no console 'Completo!'.

Agora que você já sabe como funciona a lógica por trás das promises, veremos algumas outras aplicações da mesma.

Exemplos de Aplicações de Promises

No exemplo abaixo, vamos construir uma promise a partir de um setTimeOut e em seguida resolvê-la com o then():

const timeout = (duracao) => {
return new Promise((resolve, reject) => {
setTimeout(resolve, duracao)
})
}

timeout(5000)
.then(function() { // executa o bloco após 5 segundos
console.log('passou 3 segundos')
})

Já no exemplo abaixo estamos simulando uma rejeição de promessa:

function getTodosOsProdutos () {
return new Promise((resolve, reject) => {
reject(new Error('Não foi possível recuperar a lista de produtos'))
})
}

getTodosOsProdutos()
.catch(err => console.log(err.message)) // Não foi possível recuperar a lista de produtos

Também podemos ter promessas dentro de outras, veja como é fácil:

function getTodosOsUsuarios () {
return new Promise((resolve, reject) => {
resolve([ 1, 2, 3 ])
})
}

function getUsuarioById (id) {
return new Promise((resolve, reject) => {
resolve({ id, nome: 'Micilini Roll' })
})
}

getTodosOsUsuarios()
.then(ids => getUsuarioById(ids[0]))
.then(usuario => console.log(usuario)) // { id: 1, nome: 'Micilini Roll' }

No exemplo abaixo, temos a estrutura de uma Promise:

Promise.resolve()
.then(() => [])
.then(console.log) // []

Caso você queria retornar algum tipo de erro, você pode fazer isso por meio do Promise.Reject:

Promise.reject(new Error('falha na execução do código'))
.catch(err => console.log(err.message)) // falha na execução do código

Promises com múltiplos Then()

Também é possível usar mais de um then() dentro de uma mesma promise.

Para que isso seja possível, o primeiro then deve usar o comando return de modo a executar o próximo then, e assim por diante:

function executePromise(){

return new Promise((resolve, reject) => {
resolve(10);
}).then((retorno) =>{
console.log(retorno);//10
return retorno + 10;
}).then((retorno) => {
console.log(retorno);//20
});

}

executePromise();

No caso do comando acima, só estamos usando o then(), mas poderíamos tranquilamente adicionar os outros métodos ali em conjunto, como os métodos catch() e finally();

Métodos de uma Promise

Como vimos, a Promise é um objeto que nos ajuda a lidar com promessas na nossa aplicação, e como todo objeto do JS, ele também possui alguns métodos que nos facilitam nosso desenvolvimento.

Conheceremos cada um desses métodos abaixo:

Promise.All()

Haverá casos em que a API que você estiver utilizando terá diversos endpoints diferentes, e para resolver isso, você pode fazer o uso do método all().

O método estático Promise.all() recebe um iterável de promessas como entrada e retorna uma única Promise.

const promise1 = 'Micilini';
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values);
});

// resultado Final: ['Micilini', 42, "foo"]

No caso do comando acima, ele vai resolvendo cada uma das promessas, de modo a passar os resultados finais para dentro da variável temporária values.

O all() se comporta como um interador de promessas (loop).

Veja um exemplo em que fazemos o uso do promise.all() em conjunto com o comando map do array:

const endpoints = [
"https://meusite.com/api/usuario/1",
"https://meusite.com/api/usuario/2",
"https://meusite.com/api/usuario/3",
"https://meusite.com/api/usuario/4"
]

const promises = endpoints.map(url => fetch(url).then(res => res.json()))

Promise.all(promises)
.then(body => console.log(body.name))

No comando acima, temos um array com todos os endpoints que serão percorridos pelo fetch.

Ali temos o comando .map, que percorre cada uma das URL's de modo a resolver todas as promessas em um único array.

Onde no final o promisse.all() junta todas as respostas.

Promise.Resolve()

O método Promise.resolve() retorna um objeto Promise que é resolvido com o valor passado.

Promise.resolve("Sucesso!").then(function(value) {
console.log(value); // "Sucesso!"
}, function(value) {
//Não será chamado
});

Resolvendo um array de modo a retornar cada elemento:

var p = Promise.resolve([76,23,77]);

p.then(function(v) {
console.log(v[0]);//76
});

Resolvendo uma promise:

var original = Promise.resolve(true);

var cast = Promise.resolve(original);

cast.then(function(v) {
console.log(v); // true
});

Promise.Reject()

O método Promise.reject() retorna um objeto Promise que é rejeitada com um dado motivo.

Promise.reject("Testando reject estático").then(function(motivo) {
// não executado
}, function(motivo) {
console.log(motivo); // "Testando reject estático"
});

Promise.reject(new Error("falha")).then(function(erro) {
// não executado
}, function(erro) {
console.log(erro); // Stacktrace
});

Promise.Race()

O método Promise.race() retorna uma promise que resolve ou rejeita assim que uma das promises no iterável resolver ou rejeitar, com o valor ou razão daquela promise.

var p1 = new Promise(function(resolve, reject) {
setTimeout(resolve, 500, "one");
});
var p2 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, "two");
});

Promise.race([p1, p2]).then(function(value) {
console.log(value); // "two"
// Ambos resolvem, mas p2 é mais rápido
});

Promise.AllSettled()

O método Promise.allSettled() retorna uma promessa que é resolvida após todas as promessas dadas serem resolvidas ou rejeitadas, com um array de objetos que descrevem o resultado de cada promessa.

É tipicamente usado quando você tem múltiplas tarefas assíncronas que não são dependentes das conclusões umas das outras, ou quando você sempre quer ter o resultado de cada promessa.

const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];

Promise.allSettled(promises).
then((results) => results.forEach((result) => console.log(result.status)));

//Resultado
// "fulfilled"
// "rejected"

Promise.Any()

O método estático Promise.any() recebe uma iteração de promessas como entrada e retorna uma única Promise.  

const promise1 = Promise.reject(0);
const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 'rápido'));
const promise3 = new Promise((resolve) => setTimeout(resolve, 500, 'devagar'));

const promises = [promise1, promise2, promise3];

Promise.any(promises).then((value) => console.log(value));

// Resultado: "rápido"

Promise.Catch()

O método catch() das instâncias de Promise agenda uma função a ser chamada quando a promessa é rejeitada.

const promise1 = new Promise((resolve, reject) => {
throw new Error('Error Detectado!');//Como geramos um novo Error, o bloco catch será chamado
});

promise1.catch((error) => {
console.error(error);//Resultado: 'Error Detectado!'
});

Promise.Finally()

O método finally() das instâncias de Promise agenda uma função a ser chamada quando a promessa for estabelecida (cumprida ou rejeitada).

function checkMail() {
return new Promise((resolve, reject) => {
if (Math.random() > 0.5) {
resolve('Mail has arrived');
} else {
reject(new Error('Failed to arrive'));
}
});
}

checkMail()
.then((mail) => {
console.log(mail);
})
.catch((err) => {
console.error(err);
})
.finally(() => {
console.log('Experiment completed');
});

Async e Await

Existe uma nova sintaxe que foi adicionada na versão ES2017 do Javascript, que nos permite trabalhar com promessas de forma mais confortável.

São os famosos comandos async e await, que foram feitos para simplificar o conceito de programação assíncrona, possibilitando que o desenvolvedor escreva códigos de forma síncrona, ao mesmo tempo em que seja interpretada pelo Javascript de forma assíncrona.

Funções Async

Vamos começar entendendo o conceito principal do comando async.

Ele pode ser declarado antes de uma função da seguinte maneira:

async function retornaUm(){
return 1;
}

Quando declaramos o comando async antes de uma função, estamos transformando essa função em uma promessa (promise).

Isso significa que podemos usar o método then() em conjunto com essa função, já que a partir de agora, essa função se trata de uma promise, vejamos:

async function retornaUm(){
return 1;
}

retornaUm().then((retorno) => {
console.log(retorno);
});

Caso desejar, você pode retornar uma promise de forma explicita:

async function retornaUm(){
return Promise.resolve(12);
}

retornaUm().then((retorno) => {
console.log(retorno);
});

Nos exemplos acima, estamos retornando uma promessa resolvida, mas você também pode fazer o uso do Promise.reject() em conjunto com o catch() para tratar erros, como por exemplo:

async function retornaUm(){
return Promise.reject(98);
return Promise.resolve(12);//Esse código nunca será executado pois já existe um return acima.
}

retornaUm().then((retorno) => {
console.log(retorno);
}).catch((retorno) => {
console.log('Error: ' + retorno);
});

Lembrando que também podemos usar o método finally() em conjunto com a função.

Portanto, podemos dizer que o comando async, garante que a função retorne uma promessa.

Agora, nós vamos conhecer um outro comando chamado await, que funciona apenas dentro de funções assíncronas, e seu uso é bem legal 😁

Await

O comando await faz com que o Javascript espere até que uma determinada promessa seja estabelecida e retorne o seu resultado.

No exemplo abaixo, estamos criando uma promessa dentro de outra, aonde a segunda promessa tem a obrigação de aguardar o seu retorno:

async function retornaResultado() {

let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("Feito!"), 6000);//Promessa vai ser resolvida dentro de 6 segundos
});

let resultado = await promise; // esperar até que a promessa acima seja resolvida (*)

console.log(resultado); // "Feito!"
}

retornaResultado();

Sem o comando await, o valor armazenado dentro da variável resultado seria undefined, uma vez que a promessa ainda não foi resolvida ainda.

O comando await suspende literalmente a execução da função até que a promessa se estabeleça, e então retoma como o resultado daquela promessa.

Em quesito de utilização de CPU, não custa nenhum recurso, porque o mecanismo do JS pode fazer outras tarefas nesse meio tempo, como executar outros scripts, lidar com eventos e etc.

Veja um outro exemplo utilizando a API Fetch:

async function pegarUsuario(idUser) {
let resposta = await fetch(`https://meusite.com/usuario/${idUser}`);
let dadosUsuario = await resposta.json();
return dadosUsuario.idade; // nas linhas de return não é necessário usar await
}

Resolvendo varias promessas com Async/Await

Para resolver diversas promessas, podemos usar o método all():

async function pegarUsuario(idUser) {
let resposta = await fetch(`https://meusite.com/usuario/${idUser}`);
let dadosUsuario = await resposta.json();
return dadosUsuario;
}

let [usuarioUm, usuarioDois] = await Promise.all([pegarUsuario(1), pegarUsuario(2)]);

Como vimos os comandos async/await surgiram como uma opção mais "legível" ao .then(), sendo possível usar ambos dentro de um mesmo código.

Conclusão

Sim, para alguns, os conceitos de síncrono e assíncrono podem parecer um tanto quanto complexos em um primeiro momento.

Mas depois que você aprende como eles funcionam de verdade, creio que você nunca mais os esquece, é que nem andar de bicicleta.

Neste conteúdo você aprendeu um pouco mais sobre alguns comandos que executam códigos de maneira assíncrona, tais como promises e o uso dos comandos async/await

Pronto para a próxima jornada?