Como usar async e await com vanilla JavaScript
Tags: javascript, programming, portugues
Published: 2023-12-09 16:54:50

_**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.

```javascript
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();
```

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.

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

getPost();
```

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á.

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

getPost();
```

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.

```javascript
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();
```

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.

```javascript
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();
```

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()`.

```javascript
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();
```

## 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.

```javascript
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();
```

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.

```javascript
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();
```

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()`.

```javascript
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();
```

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

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

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.

```javascript
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()
```

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.

```javascript
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();
```

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](https://gomakethings.com/)