Trabalhando com Promises em JavaScript

Javascript

Um assunto que muito vem sendo falado é esse tal de Promises (Promessas) em JavaScript, mas afinal, do que se trata? O que eu vou ficar prometendo? Porque e quando eu devo utilizar Promises? Se você tem as mesmas ou algumas dessas dúvidas, irei tentar repondê-las de forma simplista e resumida.

O que é uma Promise?

É um objeto usado para realizar processamentos assíncronos, esse objeto guarda um valor que pode estar disponível agora, no futuro ou nunca. Isso permite o tratamento de eventos ou ações que acontecem de forma assíncrona em casos de sucessos ou falhas.

Uma Promise também possuí diferentes estados, sendo alguns deles:

  • Pendente (Pending).
  • Resolvida (Resolved) (não está na documentação, mas gosto de definir esse estado também).
  • Rejeitada (Rejected).
  • Realizada (Fulfilled).
  • Estabelecida (Settled).

Geralmente os estados mais utilizados são dois (2), sendo eles: Resolvida e Rejeitada.

O que uma Promise faz?

A Promise realiza processamentos e tratamentos de eventos ou ações assíncronas.

Como uma Promise faz?

Ao criar uma Promise, a mesma começa em estado inicial como pendente (pending), assim, os estados que ela pode ir são os demais informados anteriormente. Se ela estiver no estado de resolvida (resolved) é porque tudo deu certo, ou seja, a Promise foi criada e processada com sucesso, porém, em casos de falhas, a mesma estará no estado de rejeitada (rejected).

Uma das maneiras de fazer esse tratamento é através das funções then e catch, para sucesso ou falha respectivamente (mais á frente será exemplificado e explicado).

Saiba mais

O estado realizada (fulfilled) é quando a Promise foi resolvida, ou seja, assim que invocamos a função resolved da mesma.

O estado estabelecida (settled) é quando a Promise foi resolvida ou rejeitada, ou seja, se o seu estado não está como pendente.

Sim, esses dois estados são um pouco confusos, mas não se preocupe, no dia-à-dia vamos utilizar mais os estados de Resolvida e Rejeitada.

Porque utilizar uma Promise?

Quando utilizamos funções assíncronas, não conseguimos garantir o fluxo de nosso código, ou seja, um trecho de código que está localizado após uma função assíncrona pode ser executado antes da mesma ter sido realizada, por exemplo:

let cepBuscado;
console.log("Buscando CEP");
cepBuscado = buscarCep("13845373");
console.log("CEP buscado");
console.log("CEP encontrado: ", cepBuscado);

function buscarCep(parametro) {
    let cep;
    fetch(`http://ws.matheuscastiglioni.com.br/ws/cep/find/${parametro}/json`)
        .then(response => response.json())
        .then(data => cep = data.cep)
        .catch(console.error);
    return cep;
}

Esse código será executado assim que nossa página carregar, veja a ordem de execução:

Exemplo de porque utilizzar Promises

Repare que o cep foi impresso como undefined isso porque a função ainda não foi finalizada.

Quando utilizar uma Promise?

Podemos utilizar Promises para diversos motivos, um deles que eu acho bem interessante é para realizar animações de processamento ou carregamento, deixamos um ícone com a animação até a Promise ser realizada, dando uma boa interatividade ao o usuário.

Criando minha primeira Promise

Legal, sabemos toda a parte teórica, vamos começar a escrever nossos códigos, ou seja, criar nossa primeira Promise.

Para exemplo, vou utilizar uma função que simula uma requisição HTTP:

function fazRequisicao() {

}

Assim podemos começar a criação de nossa Promise, mas, como vamos criá-la? Se a Promise é um objeto em JavaScript e objetos em JavaScript podem ser criados ou instânciados através da palavra reservada new, será que a Promise não funciona da mesma maneira? Sim, é dessa maneira que criamos um novo objeto do tipo Promise.

new Promise();

Mas somente isso não será o suficiente, se tentarmos chamar a função fazRequisicao:

function fazRequisicao() {
    new Promise();
}

fazRequisicao();

Um erro no console do navegador será lançado:

Erro Promise incomplea

Ué, porque será que esse erro aconteceu? Um detalhe sobre Promises é que na sua criação precisamos passar como parâmetro uma função que será responsável por resolver (resolve) ou rejeitar (reject) a Promise:

function fazRequisicao() {
    new Promise((resolve, reject) => {

    });
}

Estou utilizando Arrow Functions por questões de preferência, nada impede você de utilizar funções (functions) normais do JavaScript.

Pronto, agora o erro não irá mais acontecer, para confirmar podemos logar a resposta de nossa função que será uma Promise:

console.log(fazRequisicao());

Chamando função sem retorno

Eita, chamamos a função e seu retorno foi undefined, o que está acontecendo? Simples, dentro de nossa função fazRequisicao não informamos seu retorno, ou seja, precisamos devolver a Promise que estamos criando:

function fazRequisicao() {
    return new Promise((resolve, reject) => {

    });
}

Agora se olharmos nosso console novamente, uma Promise com estado de Pendente será mostrada:

Promise em estado de pendente

Resolvendo a Promise

Para resolver uma Promise podemos utilizar a função resolve, passando como parâmetro um valor que será acessível através de nossa Promise resolvida:

function fazRequisicao() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("Promise resolvida");
        }, 5000);
    });
}

Veja que para simular uma requisição adicionei um setTimeout para aguardar cinco (5) segundos antes de resolver a Promise.

Legal, nossa Promise está resolvida, mas como podemos pegar o valor passado para a função resolve? Para isso, podemos utilizar a função then das Promises, como foi mencionado no começo do post:

fazRequisicao()
    .then(console.log);

Veja que ao realizar o then estamos fazendo um console.log, ou seja, estamos logando no console do navegador qualquer resposta que venha dentro de nossa função resolve. Após esperar cinco (5) segundos a mensagem de Promise resolvida deve ser impressa no console.

Reforçando: Qualquer valor passado para a função resolve será acessível como parâmetro da função .then, o valor pode ser uma String, Number, Booleano, Function (sim, podemos passar outra função), Object, etc...

Rejeitando a Promise

Assim como fizemos para resolver a Promise, também podemos rejeitá-la. Para realizar a simulação de rejeição será necessário fazer algumas modificações em nossa função, a primeira delas é adicionar um parâmetro informando se a Promise deve ser resolvida ou não:

function fazRequisicao(resolver = true) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("Promise resolvida");                
        }, 5000);
    });
}

Também foi adicionado um valor padrão (default) para o parâmetro, ou seja, se o mesmo não for informado seu valor será true (verdadeiro) sendo assim precisamos fazer um pequeno if para tratar essa condição.

function fazRequisicao(resolver = true) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (!resolver) {
                // rejeitá-la
            }
            resolve("Promise resolvida");                
        }, 5000);
    });
}

Veja que evitamos adicionar if e else, isso também é chamado de Early Return.

Legal, as modificações estão prontas, agora podemos de fato rejeitá-la, isso pode ser feito de duas maneiras:

Utilizando a função reject (da mesma forma que a resolve):

function fazRequisicao(resolver = true) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (!resolver) {
                reject("Deu erro");
            }
            resolve("Promise resolvida");                
        }, 5000);
    });
}

Ou lançando um erro:

function fazRequisicao(resolver = true) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (!resolver) {
                throw new Error("Deu erro");
            }
            resolve("Promise resolvida");                
        }, 5000);
    });
}

Legal, a mesma foi resolvida e como podemos realizar esse tratamento? Assim como temos o then para sucesso, também temos o catch para erros:

fazRequisicao(false)
    .then(console.log)
    .catch(console.error);

Veja que o parâmetro está sendo passado false (falso) sendo assim a Promise da função não deve ser resolvida, após aguardar os mesmos cinco (5) segundos uma mensagem no console como erro deve ser impressa.

Resumindo

Tanto o resolve com o then ou reject com catch funcionam em conjunto da mesma maneira, porém cada um tendo seu propósito, o primeiro para sucesso e o segundo para erro.

Para saber mais

Caso não queira utilizar o catch para tratar erros a função then recebe como segundo parâmetro uma função que deve ser executado em casos de erros, ou seja:

Para imprimir a mensagem de sucesso:

fazRequisicao()
    .then(console.log, console.error);

E para imprimir a mensagem de erro:

fazRequisicao(false)
    .then(console.log, console.error);

Veja que omitimos o .catcth e adicionamos o tratamento de erro como segundo parâmetro da função .then.

Além disso, podemos executar diversos trechos de códigos dentro de then ou catch:

fazRequisicao(false)
    .then(resposta => {
        // fazer qualquer coisa com a resposta ou qualquer código
    })
    .catch(erro => {
        // fazer qualquer coisa com o erro ou qualquer código
    });

Novamente estou utilizando Arrow Functions por gosto, fique a vontade de utilizar funções normais do JavaScript.

Para finalizar o then pode devolver uma nova Promise que será acessível ao próximo then, em outras palavras, podemos realizar encadeamentos de then's retornando novas Promises:

fazRequisicao(false)
    .then(resposta => {
        // retornar outra Promise
    })
    .then( /* Pegar o retorno da Promise anterior */ )
    .catch(erro => {
        // fazer qualquer coisa com o erro ou qualquer código
    });

Além claro de possuir outros recursos.

Conclusão

Trabalhar com Promises em JavaScript pode ser muito útil e faciliar muito a nossa vida durante o desenvolvimento, esses foram apenas alguns exemplos, na documentação completa você encontrá mais recursos e possibilidades, espero que tenha gostado.

Não deixe de comentar e compartilhar, além claro, de se inscrever na newsletter para receber novidades por email.

Até a próxima \o/.

Matheus Castiglioni

Matheus Castiglioni

Apaixonado pelo mundo dos códigos e um eterno estudante, gosto de aprender e saber um pouco de tudo, aquela curiosidade de saber como tudo funciona, tento compartilhar o máximo de conhecimentos adquiridos e ajudar todos aqueles que sou capaz.