Como usar async e await com vanilla JavaScript

dougsource

doug-source

Posted on December 9, 2023

Como usar async e await com vanilla JavaScript

Nota: apenas traduzi o texto abaixo e postei aqui.

Você pode ter visto async e await antes e ficou confuso sobre o que eles fazem ou por que você usaria eles. Hoje vou desmistificar um pouco as coisas com alguns exemplos práticos.

O que async e await fazem

O await operator transforma uma function tradicional em uma function assíncrona baseada em Promise.

O await operator diz a um script para aguardar a resolução de uma promise antes de prosseguir (mais ou menos como .then() faz). Quando usado com um variable operator (como var ou let), ele atribui a resposta da promise à variável em vez da própria Promise.

O await operator só pode ser usado dentro de uma async function.

Os operators async e await são úteis para permitir que você escreva functions baseadas em Promise da mesma forma que escreveria functions síncronas.

Ainda está confuso? Vamos esclarecer as coisas com um exemplo real.

Usando dados de uma request fetch() para fazer outra request

Por exemplo, quando usamos dados de uma request fetch() para fazer outra e, em seguida, podemos combinar todos os dados.

Este foi o resultado final.

var getPost = function () {
    var post;

    // Chama a API
    fetch(
        'https://jsonplaceholder.typicode.com/posts/5'
    ).then(function (response) {
        if (response.ok) {
            return response.json();
        }
        return Promise.reject(response);
    }).then(function (data) {
        // Armazena os dados para a variável
        post = data;

        // Fetch outra API
        return fetch(
            'https://jsonplaceholder.typicode.com/users/' + data.userId
        );
    }).then(function (response) {
        if (response.ok) {
            return response.json();
        }
        return Promise.reject(response);
    }).then(function (userData) {
        console.log(post, userData);
    }).catch(function (error) {
        console.warn(error);
    });
};

getPost();
Enter fullscreen mode Exit fullscreen mode

Vamos usar async e await para escrever este script de uma maneira diferente.

Usando async e await com fetch() para chamar uma API

A primeira coisa que faremos é adicionar o async operator antes do nosso function operator. Isso transforma a function em uma async function e baseada em Promise.

var getPost = async function () {
    // O código vai aqui...
};

getPost();
Enter fullscreen mode Exit fullscreen mode

A seguir, faremos nossa chamada fetch() com a API. Em vez de usar then(), iremos prefixá-lo com await e atribuí-lo a uma variável.

Agora, nossa function assíncrona getPost() esperará até que fetch() retorne sua resposta para atribuir um valor à postResp e executar o restante do script. E em vez de atribuir a promise que fetch() retorna, ele atribuirá a resposta real que receberá.

var getPost = async function () {
    // Obtêm os dados do post
    var postResp = await fetch(
        'https://jsonplaceholder.typicode.com/posts/5'
    );
};

getPost();
Enter fullscreen mode Exit fullscreen mode

A seguir, podemos usar o método json() nessa "response" para obter os dados reais. O método json() também é baseado em Promise, então precisaremos usar await com ele também.

var getPost = async function () {
    // Obtêm os dados do post
    var postResp = await fetch(
        'https://jsonplaceholder.typicode.com/posts/5'
    );
    var post = await postResp.json();
};

getPost();
Enter fullscreen mode Exit fullscreen mode

Agora temos dados de volta, atribuídos para "post". Podemos usar post.userId para fazer nossa segunda chamada de API.

Iremos prefixá-lo novamente com await e, em seguida, usaremos o método json() para obter os dados dele.

Quando ambas as chamadas de API forem concluídas, podemos registrar os dados de post e de author no console.

var getPost = async function () {
    // Obtêm os dados do post
    var postResp = await fetch(
        'https://jsonplaceholder.typicode.com/posts/5'
    );
    var post = await postResp.json();

    // Obtêm o author
    var authorResp = await fetch(
        'https://jsonplaceholder.typicode.com/users/' + post.userId
    );
    var author = await authorResp.json();

    console.log(post, author);
};

getPost();
Enter fullscreen mode Exit fullscreen mode

Como você pode ver, isso tem uma sintaxe agradável, simples e legível. Mas há um problema com este setup.

Manipulação de Error

O setup atual será interrompido de maneira muito desagradável se houver um "error" na "API response".

Por exemplo, se você escreveu o endpoint /postses em vez de /posts, post e author serão undefined. Se você digitasse incorretamente a URL ou se a API estivesse inativa, o script seria interrompido antes de chegar à parte console.log().

var getPost = async function () {
    // Obtêm os dados do post
    var postResp = await fetch(
        'https://jsonplaceholder.typicode.com/postses/5'
    );
    var post = await postResp.json();

    // Obtêm o author
    var authorResp = await fetch(
        'https://jsonplaceholder.typicode.com/users/' + post.userId
    );
    var author = await authorResp.json();

    console.log(post, author);
};

getPost();
Enter fullscreen mode Exit fullscreen mode

Compatibilidade de Navegadores

Os métodos async e await funcionam em todos os navegadores modernos, mas não têm suporte para IE. Eles não podem ser "polyfilled" com algum polyfill e devem ser transpilados usando uma ferramenta como Babel se você quiser que sejam executados em navegadores mais antigos.

Usando then() e catch()

A solução mais óbvia é usar o método projetado especificamente para manipular "catching errors" com Promises: catch().

Se a resposta for bem-sucedida, você pode retornar response.json(). Caso contrário, você usará catch() para logar um error, e post e author serão definidos como undefined.

Você pode verificar se post e author têm valores antes de prosseguir no seu script.

var getPost = async function () {
    // Obtêm os dados do post
    var post = await fetch(
        'https://jsonplaceholder.typicode.com/posts/5'
    ).then(function (response) {
        return response.json();
    }).catch(function (err) {
        console.warn('Não foi possível encontrar um post');
    });

    // Se não tiver post, avisa
    if (!post) return;

    var author = await fetch(
        'https://jsonplaceholder.typicode.com/users/' + post.userId
    ).then(function (response) {
        return response.json();
    }).catch(function (err) {
        console.warn('Não foi possível encontrar um autor');
    });

    // Se não houver author, avisa
    if (!author) return;

    console.log(post, author);
};

getPost();
Enter fullscreen mode Exit fullscreen mode

O método fetch() só gera um error se a chamada não for resolvida. Uma "response" que retorne um status code de 400 ou de 500 ainda seria considerada um "success".

Também podemos verificar nossos métodos then() para ter certeza de que a propriedade response.ok é true e forçar um error se não for.

var getPost = async function () {
    // Obtêm os dados
    var post = await fetch(
        'https://jsonplaceholder.typicode.com/posts/5'
    ).then(function (response) {
        if (response.ok) {
            return response.json();
        }
        return Promise.reject(response);
    }).catch(function (err) {
        console.warn('Could not find a post');
    });

    // Se não tiver post, avisa
    if (!post) return;

    var author = await fetch(
        'https://jsonplaceholder.typicode.com/users/' + post.userId
    ).then(function (response) {
        if (response.ok) {
            return response.json();
        }
        return Promise.reject(response);
    }).catch(function (err) {
        console.warn('Não foi possível encontrar um author');
    });

    // Se não houver author, avisa
    if (!author) return;

    console.log(post, author);
};

getPost();
Enter fullscreen mode Exit fullscreen mode

Então, isso funciona. Mas como você pode ver, é absurdamente verboso.

Uma maneira mais compacta de manipular errors

O truque começa colocando nosso método fetch() entre parênteses (()) e chamando o método catch() diretamente nele. Para manter nosso código DRY, passaremos uma callback function chamada handleError() para o método catch().

var getPost = async function () {
    // Obtêm os dados de post
    var post = await (fetch(
        'https://jsonplaceholder.typicode.com/posts/5'
    ).catch(handleError));

    // Obtêm o author
    var author = await (fetch(
        'https://jsonplaceholder.typicode.com/users/' + post.userId
    ).catch(handleError));

    console.log(post, author);
};

getPost();
Enter fullscreen mode Exit fullscreen mode

Dentro do método handleError(), retornaremos um novo Response() object. No object, aplicaremos "stringify" a um object com um error code e uma message.

var handleError = function (err) {
    console.warn(err);
    return new Response(JSON.stringify({
        code: 400,
        message: 'Network Error estúpido'
    }));
};
Enter fullscreen mode Exit fullscreen mode

Em seguida, prefixamos fetch() com await dentro dos parênteses e attach nosso método json() fora deles.

Se a chamada fetch() for bem-sucedida, ela retornará automaticamente um "stringified response object" para "parse". Caso contrário, nosso método handleError() criará e retornará também um "stringified response object" para "parse" posteriormente também pelo método json().

De qualquer forma, o método json() possui um "stringified object" para parse, portanto nenhum error será gerado.

var getPost = async function () {
    // Obtêm os dados de post
    var post = await (await fetch(
        'https://jsonplaceholder.typicode.com/posts/5'
    ).catch(handleError)).json();

    // Obtêm o author
    var author = await (await fetch(
        'https://jsonplaceholder.typicode.com/users/' + post.userId
    ).catch(handleError)).json();

    console.log(post, author);
};

getPost()
Enter fullscreen mode Exit fullscreen mode

Por fim, após cada chamada, verificaremos se os dados retornados possuem uma propriedade code com valor 400.

Se isso acontecer, houve um error e não avançaremos mais. Caso contrário, estamos prontos para seguir adiante.

var getPost = async function () {
    // Obtêm os dados de post
    var post = await (await fetch(
        'https://jsonplaceholder.typicode.com/posts/5'
    ).catch(handleError)).json();
    if (post.code && post.code === 400) return;

    // Obtêm o author
    var author = await (await fetch(
        'https://jsonplaceholder.typicode.com/users/' + post.userId
    ).catch(handleError)).json();
    if (author.code && author.code === 400) return;

    console.log(post, author);
};

getPost();
Enter fullscreen mode Exit fullscreen mode

O único problema aqui é que se o método retornou com sucesso, mas não estava ok, esta abordagem não detectará isso.

Qual abordagem você deve usar?

Pessoalmente, ainda uso a abordagem tradicional then() e catch() com métodos fetch() retornados.

Acho que a forma como se lê — "primeiro faz isso, depois faça isso, depois faça aquilo" — é mais fácil de entender.

Também acho que oferece tratamento de errors mais direto e tem melhor compatibilidade com versões anteriores sem a necessidade de transpilar o código. Pode ser "polyfilled", se necessário.

Eu gosto que async e await lêem semelhante às functions síncronas, mas também não acho then() e catch() mais difíceis de ler.

Fonte

Newsletter de Go Make Things

💖 💪 🙅 🚩
dougsource
doug-source

Posted on December 9, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related