DEV Community

Cover image for Introdução a Promises, Async / Await no JavaScript
Alan Pereira
Alan Pereira

Posted on • Edited on

Introdução a Promises, Async / Await no JavaScript

Sabe a sensação de nunca ter entendido um assunto importantíssimo da maneira como deveria? Pois é. Sou eu com Promises. Neste sentido, resolvi escrever esse artigo para que entendamos com calma do que se trata essas tais promessas.

Mas antes de entrar nesse tópico, é de fundamental importância entender como funcionam as callbacks no JavaScript. Felizmente, eu já tenho um artigo sobre isso que você pode conferir aqui.

Então vamos ao assunto!

O que são as Promises, afinal?

Promise é uma ferramenta extremamente útil e muito usada em JavaScript pois permite que se execute uma sequência de comandos de forma assíncrona. Em outras palavras, permite executar duas partes do seu código ao mesmo tempo.

Neste sentido, você terá diversas vantagens como manipular o seu código de maneira mais eficiente, manipular os dados em chamadas de APIs e tratar os erros de maneira mais limpa.

Vamos a um exemplo de uma sequência de comandos síncrona (tradicional) de uma função que soma dois números e retorna uma outra função com a resposta:

function sumNumbers() {
    let result = 1 + 1;

    if (result == 2) {
        successCallback();
    } else {
        errorCallback();
    }
}

function successCallback() {
    console.log("Funcionou. A soma de 1 + 1 é 2")
}

function errorCallback() {
    console.log("Oops! Algo deu errado.");
}

sumNumbers();

// output: Funcionou. A soma de 1 + 1 é 2
Enter fullscreen mode Exit fullscreen mode

Agora vamos refazer o código anterior para que desta vez ele retorne uma Promise. Esta Promise simplificará o uso dos callbacks, veja só.

Para isso, se cria um new Promise, que é uma classe que retornará um objeto. Na criação desta classe passaremos como parâmetro uma função anônima, que por sua vez receberá os parâmetros resolve e reject.

Ficou confuso? Calma que no exemplo ficará mais claro, mas por enquanto atente-se a esses parâmetros resolvee reject. O que esses parâmetros querem dizer? Querem dizer que se o código correr da maneira como deveria, ele executará o trecho resolve do código. Caso algum erro aconteça, ele executará o trecho reject.

Esses caras resolve e reject são o que chamamos de callbacks.

let p = new Promise((resolve, reject) => {
    let a = 1 + 1;
    if (a == 2) {
        resolve('Success');
    } else {
        reject('Failed');
    }
});
Enter fullscreen mode Exit fullscreen mode

Por enquanto entramos somente na parte da criação da Promise, agora vamos chamá-la. com o .then, podemos forçar que o código só prossiga com a execução da função após que uma anterior seja resolvida. Veremos isso mais à frente, mas por enquanto se atente a forma como fazemos a chamada da Promise:

p.then((message) => {
    console.log('Esse é o then: ' + message);
}).catch((err) => {
    console.log('Esse é o catch: ' + err);
});

// output: Esse é o then: Success

// Caso mudássemos a condicional a == 2 para qualquer outro valor, provavelmente o output seria o do catch:
// output: Esse é o catch: Failed
Enter fullscreen mode Exit fullscreen mode

Criando Promises

Por enquanto vimos os conceitos básicos de promise como por exemplo usá-las através do .then e .catch, mas vamos adicionar uma pitada de complexidade e tentar ir para um exemplo mais robusto de como fazer com que qualquer função retorne uma Promise.

Neste caso temos duas funções callbacks síncronas chamadas errorCallback e callback. Elas retornarão um objeto com a propriedade name e message. Uma função comum com sua chamada seria algo assim:

const myName = 'Alan';

function whoAreYouCallback(callback, errorCallback) {
    if (myName != 'Alan') {
        errorCallback({
            name: 'Algo deu errado',
            message: myName + ' não é meu nome.'
        })
    } else {
        callback({
            name: myName,
            message: 'Olá, mundo!'
        });
    }
}

whoAreYouCallback((result) => {
    console.log(result.name + "? Sou eu! " + result.message);
}, (error) => {
    console.log(error.name + '. ' + error.message);
})

// output: Alan? Sou eu! Olá, mundo!
Enter fullscreen mode Exit fullscreen mode

Agora para retornar uma promise, você verá que não precisaremos adicionar os errorCallback e callback nos parâmetros da função, apenas faremos com que essa função retorne uma Promise diretamente.

Para retornar uma promise, adicionamos o return new Promise(função anônima) com as callbacks de parâmetro da função anônima.

Para a chamada de Promise, adicionamentos o .then para tratar o cenário de sucesso e o .catch para o cenário de falha:

const myName = 'Alan';

function whoAreYouCallback() {
    return new Promise((resolve, reject) => {
        if (myName != 'Alan') {
        reject({
            name: 'Algo deu errado',
            message: myName + ' não é meu nome.'
        })
    } else {
        resolve({
            name: myName,
            message: 'Olá, mundo!'
        });
    }
    })
}

whoAreYouCallback()
    .then((result) => {
        console.log(result.name + "? Sou eu! " + result.message);
    }).catch((error) => {
        console.log(error.name + '. ' + error.message);
    })

// output: Alan? Sou eu! Olá, mundo!
Enter fullscreen mode Exit fullscreen mode

O superpoder da Promise: Determinando a ordem de chamada das funções

Agora que sabemos usar as Promises, podemos ativar o seu superpoder: Fazer com que o código prossiga somente após a execução de uma determinada promise e permitindo que você controle a ordem e quando fará a execução do seu código. Neste sentido, basta aninharmos o próximo .then sendo passado como parâmetro no resultado da promise anterior.

Neste exemplo vamos criar duas promises e chamaremos através do método tradicional .then/.catch:


// Criando a primeira promise

function bestF1DriverEver(driver) {
    return new Promise((resolve, reject) => {
        if (driver === 'Senna') {
            resolve ({
                success: true,
                driverName: 'Ayrton Senna',
                msg: driver + ' é o melhor piloto de F1 de todos os tempos!'
            });
        } else {
            reject ({
                success: false,
                msg: 'Esse não é o melhor piloto!'
            });
        }
    });
}

// Criando a segunda promise

function bestF1Car(response) {
    return new Promise((resolve, reject) => {
        if (response.success) {
            resolve('McLaren MP4/4 pilotada por ' + response.driverName);
        } else {
            reject('Resposta errada! Tente de novo')
        }
    });
}

// Chamando uma promise e depois a outra

bestF1DriverEver('Senna')
    .then(response => {
        console.log('Verificando resposta...');
        return bestF1Car(response);
    })
    .then(response => {
        console.log('Encontrando o melhor carro...');
        console.log(response);
    })
    .catch(err => {
        console.log(err.msg);
    })

// output: Verificando resposta...
// output: Encontrando o melhor carro...
// output: McLaren MP4/4 pilotada por Ayrton Senna
Enter fullscreen mode Exit fullscreen mode

Se caso quiséssemos criar mais chamadas consecutivas de promises para executar o código em uma determinada ordem somente após o término da execução de uma outra função anterior, basta adicionarmos mais .then na chamada retornando a promise anteriormente usada, como foi o caso da return bestF1Car(response).

No exemplo anterior, vimos que a segunda promise confere se o resultado da primeira promise foi um sucesso. Caso positivo, ela também seguirá no cenário de sucesso.

Porém, essa chamada de promise acaba gerando um problema: A partir da chamada de mais de uma promise, vai-se criando um aninhamento de .then retornando a próxima promise. Em uma chamada de muitas promises, isso pode tornar o código complexo e ilegível.

Async / Await

Para resolver o problema da complexidade de aninhamentos de .then, o async / await vem para simplificar o trabalho de chamada de promises.

O primeiro passo é explicitar que a função será assíncrona adicionando o prefixo async na chamada das promises, para então chamar as promises desejadas na ordem que desejada de execução utilizando o prefixo await.

Com as duas promises criadas no exemplo anterior, poderíamos chamá-las dessa forma:

async function runPromises() {

    const bestF1DriverResponse = await bestF1DriverEver('Senna');

    console.log(bestF1DriverResponse);

    const bestF1CarResponse = await bestF1Car(bestF1DriverResponse);

    console.log(bestF1CarResponse);
}

runPromises()

// output: {
//  "success": true,
//  "driverName": "Ayrton Senna",
//  "msg": "Senna é o melhor piloto de F1 de todos os tempos!"
//}

// output: McLaren MP4/4 pilotada por Ayrton Senna
Enter fullscreen mode Exit fullscreen mode

E caso o parâmetro passado faça cair no erro .catch da promise? Neste caso, é necessário envelopar nossas chamadas dentro de um bloco try / catch para fazer o tratamento do erro.

async function runPromises() {

    try {
        // chamando a promise com o parâmetro errado
        const bestF1DriverResponse = await bestF1DriverEver('Piquet');

        console.log(bestF1DriverResponse);

        const bestF1CarResponse = await bestF1Car(bestF1DriverResponse);

        console.log(bestF1CarResponse);
    } catch (err) {
        console.log(err.msg);
        // Como o parâmetro passado foi errado, ele cairá neste bloco catch
    }
}

runPromises()

// output: Esse não é o melhor piloto!
Enter fullscreen mode Exit fullscreen mode

O uso do async / await junto com try / catch é uma grande vantagem pois o try / catch irá capturar erros de promise no reject como também pode capturar outros erros também.

Guia Rápido

O que é uma promise?

É um objeto que "encapsula" o estado de uma execução (sucesso ou falha) e executa callbacks com base neste estado.

Qual é a relação entre Promise e callback?

As callbacks são parâmetros dentro da função de uma promise que são responsáveis para designar como a promise irá se comportar dependendo do seu resultado. As duas principais callbacks usadas são resolve e reject.

  • Resolve: Se a promise funcionar como esperada, essa callback irá retornar a sequência de resolução do código.

  • Reject: Se a promise não funcionar como esperada, essa callback irá retornar um erro para a chamada da promise.

Qual é a relação entre Promise e Async / Await?

Async / Await é uma forma mais simples de se chamar promises. Para usá-los, nós precisamos ter uma promise criada, uma função com o prefixo async onde será inserida as chamadas da promise com o prefixo await.

O que Async / Await faz com nosso código?

Irá executar o bloco .then sem a necessidade de aninhá-los para cada chamada de promise, tornando o código mais legível. Além disso, para tratar erros apropriadamente, é importante que envolva as chamadas da promises await em torno de um bloco try / catch.

Existe algo a mais para saber sobre promises?

Sim, recentemente o Node.JS teve um update com o Promise.all para chamar várias promises em ainda menos linhas de código.

Ah, e nunca se esqueça que você pode sempre contar com o MDN Docs para aprender de uma fonte confiável!


Conclusão

Acredito que a partir daqui cobrimos os principais tópicos acerca de Promises e Async/Await. Agora é só praticar!

Agradecimentos ao meu techlead, Edgard Leal, que sempre me apoia em meus estudos e me incentiva a ser um profissional melhor.

Agradecimentos também ao pessoal do Código Fonte TV, que com os vídeos me deram uma base importantíssima para o conteúdo deste artigo.

Teve algum problema? Comenta aqui em baixo!

Se este link foi útil pra você ou você quer apoiar o meu trabalho,
deixa seu like aí e compartilhe ❤️

Deixa seu like aí!!!

Top comments (4)

Collapse
 
grafeno30 profile image
grafeno30

Parece um artigo bem interessante, ja pensou em traduzi-lo para 😊 ingles?
Obrigada

Collapse
 
alanfabricio profile image
Alan Pereira

Com certeza, um dia farei isso! HAHAHAHA

Collapse
 
juliafmorgado profile image
Julia Furst Morgado

Otimo artigo!!

Collapse
 
edgardleal profile image
Edgard Rosberg Duarte Leal

Muito bom, parabéns