Executando verificação de segurança...
3

Dúvida sobre funções assíncronas em Javascript

Estou aprendendo sobre funções assíncronas de forma meio "aleatória" pesquisando o conteúdo pela internet. Acabei ficando em dúvida porque, em todo conteúdo que encontro é apresentado isso de uma forma diferente. As vezes encontro nesse formato:

function minhaFuncao() {
  return "Alguma coisa";
}

async function main() {
  try {
    const result = await minhaFuncao();
    console.log(result);
  } catch (error) {
    console.error(error);
  }
}

E as vezes encontro nesse formato:

function minhaFuncao() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("Hello");
    }, 2000); 
  });
}

minhaFuncao()
  .then((result) => {
    console.log(result);
  })
  .catch((error) => {
    console.error(error);
  });

A minha dúvida é: qual a diferença de escrever uma função com try/catch e uma promise com .then() e .catch()? Em que situação cada coisa é usada?

Carregando publicação patrocinada...
5

Quando vc usa o await vc transforma sua função assíncrona em uma função síncrona.

Ou seja, o await faz o programa esperar a resolução da Promise antes de continuar.

Ajustei os códigos que vc mostrou e preparei 2 exemplos para demonstrar a diferença.

EXEMPLO 1

function minhaFuncao() {
  return new Promise((resolve) => setTimeout(() => resolve("Hello"), 2000));
}

console.log(new Date(), "Início");

minhaFuncao()
  .then((result) => console.log(new Date(), result))
  .catch((error) => console.error(new Date(), error));

console.log(new Date(), "Fim");

O resultado será parecido com este:

2024-04-07T14:22:29.815Z Início
2024-04-07T14:22:29.823Z Fim
2024-04-07T14:22:31.826Z Hello

Note que "Início" e "Fim" são impresso um logo após o outro enquanto que "Hello" só aparece 2 segundos depois.

É isso que a Promisse faz, executa uma tarefa fora do fluxo normal do programa.

EXEMPLO 2

function minhaFuncao() {
  return new Promise((resolve) => setTimeout(() => resolve("Hello"), 2000));
}

console.log(new Date(), "Início");

const resultado = await minhaFuncao();
console.log(new Date(), resultado);

console.log(new Date(), "Fim");

O resultado será parecido com este:

2024-04-07T14:28:53.733Z Início
2024-04-07T14:28:55.743Z Hello
2024-04-07T14:28:55.743Z Fim

Aqui "Início" é impresso e 2 segundos depois vem "Hello" e por último "Fim".

Percebeu o que eu disse antes sobre o await esperar a resolução da Promise antes de continuar o programa?


E sobre o try/catch não tem segredo. É com essa duplinha que tratamos exceções no JavaScript.

Se vc estiver usando Promise, vc vai usar then/catch, mas quando vc usa o await, a Promise passa a se comportar como uma função síncrona e vc precisa usar try/catch para tratar possíveis erros.

Sobre quando vc deve usar cada uma, acredito que o ideal é modelar o seu programa todo de forma assíncrona pra tirar total proveito das Promise, mas pode haver situações em que isso não é possível, ou então vc sabe que a tarefa que a Promise vai realizar é tão simples e rápida que vale a pena esperar.

2
2

Opa tudo bem Bruno? Seguinte, eu tecnicamente aprendi async/await na marra sem ordem nem nada, não estou dizendo que é o certo. Portanto, a diferença entre promise e try/catch, funcionalmente são a mesma coisa, mas a promise se torna um pouco diferente.

Try/catch

Esse método, ele é utilizado em casos sincronos onde não há a utilização de async/await, mas nada diz que você precisa utilizar esse método só nessa situação. No exemplo abaixo explico como funciona o resultado da função.

async function hello_world() {
  try {
    const request = await _api-response_
    return request;
  } catch (error) {
      return error;
    }
}

Nessa função, o resultado produzido será um return com os dados esperados - se der certo - ou um resultado com os erros. Se o resultado for o esperado e você, por exemplo receber um json, você conseguirá acessar o json sem precisar realizar outro passo, nesse caso, um .then que é necessário quando se tem uma promise sem async/await.

Promise

Essencialmente a ideia é a mesma que o try/catch, mas esse método é utilizado e recomendado para funções assíncronas como em apis próprias. Normalmente se pode utilizar sem a necessidade async/await, mas você precisará encadear estruturas .then/.catch para saber os resultados.

function minhaFunçao() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("Hello");
        }, 2000);
    });

minhaFuncao()
    .then((result) => {
        console.log(result);
    })
    .catch((error) => {
        console.error(error);
    });

Neste exemplo, se você colocar a função (minhaFuncao) em um console, você perceberá que será mostrado: "Promise {<pending}". Nesse caso você não consegue saber o retorno da função, para saber você precisa realizar a estrutura de .then que mostra o resultado da promise. Entretanto, você pode evitar esse passos a mais utilizando o async/await para dizer ao request que você pode esperar as promises se resolverem e trazer o resultado esperado, sem a necessidade de fazer o .then/.catch.

Quando utilizar um ou outro?

Resumidamente, utiliza-se o método try/catch em situações síncronas que podem gerar erros que você queira tratar. Já a promise é utilizada, comumente, para funções assíncronas. Ou seja, essencialmente são a mesma coisa, são estruturas para tratar resultados diferentes, mas são utilizadas em contextos diferentes, como em contextos síncronos (try/catch) e assíncronos (promises). No fim, você pode utilizar try/catch em situações assícronas e promises em situações sincronas ou vice e versa, mas a utilizacão de promises em contextos assíncronos, torna a leitura do código mais limpa e clara demonstrando organização de código a outros programadores.

2

Complementando um pouco a resposta do EstevamOt, já que você tá começando a estudar funções assíncronas.

Quando a gente trabalha com Promise e Callbacks a ideia é que estamos avisando pro "código": olha, isso aqui pode demorar um pouco, então ao invés de bloquear a execução de tudo pode ir fazendo o que precisa e quando eu terminar eu te aviso.

Então pegando como exemplo a função:

/**
 * Imagina que é uma chamada a alguma API
 * 
 * @returns {Promise<number>}
 */
function pegaIdade() {
    return new Promise(function (resolve, reject) {
        const idade = Math.floor(Math.random() * 100);
        
        // Caso deu tudo certo
        if (idade) {
            resolve(idade);
        } else {
            reject("Idade Inválida");
        }
    });
}

A função pegaIdade faria alguma consulta a alguma API e retornaria uma Promise. A Promise aceita 2 funções de callback, uma que é executada quando a operação ocorre com sucesso (resolve) e outra para quando tem erro na operação (reject).

A Promise aceita, por meio de uma interface fluente, indicar o que será executado quando terminar. No caso de sucesso executará o then e no caso de falha pulará direto para o catch.

pegaIdade()
    .then(idade => console.log(idade))
    .catch(error => console.error(error))

O bom de usar Promise é que você pode executar todas de uma única vez e então ter só 1 callback pra executar após todas retornarem algo. Muito útil quando a ordem de execução de uma função não depende da outra.

Promise
    .all([pegaIdade(), pegaIdade(), pegaIdade()])
    .then(([idade1, idade2, idade3]) => {
        console.log("idade1: " + idade1);
        console.log("idade2: " + idade2);
        console.log("idade3: " + idade3);
    })
    .catch(error => console.error(error))

Promise também permite encadeamento do retorno, para quando você precisa tratar o dado antes de ir pra próxima etapa.

pegaIdade()
    .then((idade) => {
        if (idade < 18) {
            throw "Menores não permitidos"
        }

        // Esse return jogará uma nova resposta que será executada pelo próximo then
        return idade % 2 ? false : true;
    })
    .then(ePar => console.log(ePar))
    .catch(error => console.error(error))

Quando fazemos esse encadeamento de then é porque uma resposta depende de alguma chamada assíncrona. Mas ir encadeando um monte de then pode deixar o código meio confuso.

Aí entram o async/await marcando a função.

Dentro de uma função marcada com async podemos indicar que vamos esperar a Promise executar e retornar o seu valor ao invés de aguardar a Promise chamar nossa callback. Isso fazemos com o await. Desta forma quando temos várias chamadas a funções assíncronas e que uma chamada dependa da anterior fazemos um código mais linear ao invés de vários encadeamentos.

Mas quando fazemos desta forma o erro que a Promise dispara vira uma exceção. Por isso precisamos tratar com try...catch.

/**
 * @returns {Promise<boolean>}
 */
async function reserva() {
    try {
        const idade = await pegaIdade();
        
        if (idade < 18) {
            throw "Menores não permitidos";
        }

        // Imagina que aqui retorna um boolean
        const reservaEfetuada = await efetuaReserva();

        return reservaEfetuada
    } catch (error) {
        console.error(error);
        return false;
    }
}

Importante também ressaltar que quando marcamos uma função com async estamos transformando ela numa Promise.

1