robertoachar.dev

Async Await

Conheça tudo sobre a mais nova funcionalidade disponível no JavaScript para trabalhar com funções assíncronas.

Foto de Polina Rytova
  • Introdução
  • Escrevendo uma função assíncrona com Promises
  • Escrevendo uma função assíncrona com async/await
  • Utilizando async/await com Promises
  • Resolvendo ou rejeitando uma Promise com async/await
  • Tratamento de erros com Throw Error()
  • Tratamento de erros com try/catch
  • Utilizando async/await com function expressions
  • Entendendo o await
  • IIFE - Immediately-Invoked Function Expression
  • Utilizando async/await com classes
  • Exportando uma classe com funções assíncronas
  • Aguardando múltiplas requisições em sequência
  • Aguardando múltiplas requisições simultâneas
  • Aguardando múltiplas requisições simultâneas usando Promise.all()
  • Conclusão

Introdução

Async/Await é uma das novas funcionalidades do ES2017. Com ela é possível escrever código assíncrono como se estivéssemos escrevendo código síncrono. Essa funcionalidade já está disponível a partir da versão 7.6 do Node.js.

Neste artigo pretendo demonstrar todas as possibilidades que aprendi até agora para trabalhar com async/await. Para todos os exemplos abaixo vou utilizar a API do Star Wars: https://swapi.co/. A API tem endpoints para filmes, personagens, planetas, espécies, veículos e naves. Todos os exemplos abaixo estão disponíveis no meu GitHub.

Escrevendo uma função assíncrona com Promises

Antes de introduzir o assunto principal, vamos ver um exemplo de função assíncrona utilizando Promises.

const fetch = require('node-fetch');
function getPerson(id) {
fetch(`http://swapi.co/api/people/${id}`)
.then((response) => response.json())
.then((person) => console.log(person.name));
}
getPerson(1);

No código acima, a função getPerson() faz uma chamada na API, processa o resultado e exibe o nome do personagem. Esse é um cenário bem comum encontrado hoje em dia.

Executando o código, temos o seguinte resultado.

$ node sample.js
Luke Skywalker

Escrevendo uma função assíncrona com async/await

Agora iremos escrever a mesma função, mas utilizando async/await.

const fetch = require('node-fetch');
async function getPerson(id) {
const response = await fetch(`http://swapi.co/api/people/${id}`);
const person = await response.json();
console.log(person.name);
}
getPerson(1);

O primeiro passo é converter a declaração function para async function. Desta forma estamos definindo que esta função será assíncrona.

// Promise
function getPerson(id) {...}
// async/await
async function getPerson(id) {...}

O próximo passo é utilizar await para cada processamento assíncrono dentro da função.

// Promise
fetch(`http://swapi.co/api/people/${id}`)
.then((response) => response.json())
.then((person) => console.log(person.name));
// async/await
const response = await fetch(`http://swapi.co/api/people/${id}`);
const person = await response.json();
console.log(person.name);

A própria leitura/interpretação do código fica mais fácil utilizando async/await. É como se estivéssemos programando de forma síncrona.

Executando o código, ainda temos o mesmo resultado.

$ node sample.js
Luke Skywalker

Utilizando async/await com Promises

Podemos combinar os dois mundos e utilizar async/await junto com Promises.

const fetch = require('node-fetch');
async function getPerson(id) {
const response = await fetch(`http://swapi.co/api/people/${id}`);
const person = await response.json();
return person;
}
getPerson(1).then((person) => console.log(person.name));

Funções assíncronas sempre retornam Promises.

Nesse exemplo, estamos retornando o objeto person da função assíncrona e utilizando Promises para exibir o resultado no Console, pois o retorno da função é uma Promise.

Executando o código, ainda temos o mesmo resultado.

$ node sample.js
Luke Skywalker

No exemplo acima, estamos armazenando o retorno da chamada response.json() na variável person e retornando-a. Podemos simplificar e retornar diretamente o resultado da chamada response.json().

async function getPerson(id) {
const response = await fetch(`http://swapi.co/api/people/${id}`);
return await response.json();
}

Executando o código, ainda temos o mesmo resultado.

$ node sample.js
Luke Skywalker

Resolvendo ou rejeitando uma Promise com async/await

Para resolver uma Promise com async/await:

async function getPerson(id) {
return id;
}
getPerson(1).then((id) => console.log(id)); // 1

Para rejeitar uma Promise com async/await:

async function getPerson(id) {
throw Error('Not found');
}
getPerson(0).catch((err) => console.error(err.message)); // Not found

Tratamento de erros com Throw Error()

Uma maneira de tratar erros com funções assíncronas é utilizando throw Error(). O código abaixo irá tentar recuperar um personagem com id = 0.

const fetch = require('node-fetch');
async function getPerson(id) {
const response = await fetch(`http://swapi.co/api/people/${id}`);
return await response.json();
}
// id = 0
getPerson(0).then((person) => console.log(person.name));

Executando o código, temos o seguinte resultado.

$ node sample.js
undefined

Recebemos undefined, pois como não existe nenhum personagem com id = 0, a propriedade name não será preenchida.

Alterando a saída do console de person.name para person podemos entender o que está acontecendo.

...
// id = 0
getPerson(0)
.then(person => console.log(person));

Executando o código, temos o seguinte resultado.

$ node sample.js
{ detail: 'Not found' }

Agora ficou mais claro o motivo de termos recebido undefined anteriormente. O objeto person possui apenas a propriedade detail com o valor "Not found".

Nesse caso, podemos utilizar o throw Error() para tratar esse erro.

const fetch = require('node-fetch');
async function getPerson(id) {
const response = await fetch(`http://swapi.co/api/people/${id}`);
const body = await response.json();
if (response.status !== 200) {
throw Error(body.detail);
}
return body;
}

Armazenamos o retorno da chamada response.json() na variável body e testamos o status do response. Se o status for diferente de 200 (OK), disparamos um erro com o conteúdo de body.detail, caso contrário retornamos o body.

Na primeira execução, passaremos o valor correto. O status do response será igual à 200 (OK) e o body será retornado, exibindo o nome do personagem.

getPerson(1)
.then((person) => console.log(person.name)) //Luke Skywalker
.catch((err) => console.error(err.message));

Na segunda execução, passaremos o valor errado. O status do response será diferente de 200 (OK) e um erro será disparado com o conteúdo de body.detail. Esse erro será capturado no catch e a mensagem "Not found" será exibida.

getPerson(0)
.then((person) => console.log(person.name))
.catch((err) => console.error(err.message)); // Not found

Tratamento de erros com try/catch

Outra alternativa para tratar erros com funções assíncronas é utilizando try/catch.

const fetch = require('node-fetch');
async function getPerson(id) {...}
async function loadPerson(id) {
try {
const person = await getPerson(id);
console.log(person.name);
} catch (err) {
console.error(err.message);
}
}
loadPerson(0);
loadPerson(1);

No exemplo acima, a função getPerson() permanece a mesma e introduzimos uma nova função assíncrona loadPerson() que fará o tratamento de erro.

Executando o código, temos o seguinte resultado.

$ node sample.js
Not found
Luke Skywalker

Utilizando async/await com function expressions

Neste exemplo estamos atribuindo a função assíncrona para a variável getPerson. A chamada para a função é a mesma e o resultado também.

const fetch = require('node-fetch');
// regular function
const getPerson = async function(id) {
const response = await fetch(`http://swapi.co/api/people/${id}`);
return await response.json();
};
getPerson(1).then((person) => console.log(person.name));

Executando o código, temos o seguinte resultado.

$ node sample.js
Luke Skywalker

Podemos utilizar arrow functions também.

const fetch = require('node-fetch');
// arrow function
const getPerson = async (id) => {
const response = await fetch(`http://swapi.co/api/people/${id}`);
return await response.json();
};
getPerson(1).then((person) => console.log(person.name));

Executando o código, ainda temos o mesmo resultado.

$ node sample.js
Luke Skywalker

Entendendo o await

Nesse exemplo tentaremos utilizar o await fora do escopo de uma função com async.

const fetch = require('node-fetch');
const getPerson = async (id) => {
const response = await fetch(`http://swapi.co/api/people/${id}`);
return await response.json();
};
const person = await getPerson(1);
console.log(person.name);

Executando o código, temos o seguinte resultado.

$ node sample.js
const person = await getPerson(1);
^^^^^^^
SyntaxError: Unexpected identifier

Await só pode ser utilizado dentro de funções com Async.

O resultado é o erro acima, pois só podemos utilizar o await dentro do escopo de funções com async. No caso acima poderíamos ter utilizado Promise no lugar do await.

IIFE - Immediately-Invoked Function Expression

IIFE são funções que são invocadas imediatamente após a execução do programa. Para refrescar sua memória, podemos utilizar os seguintes exemplos:

  • Utilizando jQuery
$('document').ready(function() {
// code
});
  • Utilizando JavaScript
(function() {
// code
})();

A IIFE de funções assíncronas é idêntica ao JavaScript, adicionando async na frente de function.

(async function() {
// code
})();

Podemos utilizar arrow functions também.

(async () => {
// code
})();

Como só podemos utilizar await dentro do escopo de funções com async, para resolver o caso acima, precisamos utilizar IIFE - Immediately-Invoked Function Expression.

const fetch = require('node-fetch');
const getPerson = async (id) => {
const response = await fetch(`http://swapi.co/api/people/${id}`);
return await response.json();
};
// IIFE - Immediately-Invoked Function Expression
(async function() {
const person = await getPerson(1);
console.log(person.name);
})();

Executando o código, ainda temos o mesmo resultado.

$ node sample.js
Luke Skywalker

Utilizando async/await com classes

É muito simples utilizar async/await com classes, basta adicionar async no início das funções. Neste exemplo, criamos uma classe chamada StarWars e definimos a função assíncrona getPerson(). Dentro do IIFE, instanciamos a classe StarWars e utilizamos o await.

const fetch = require('node-fetch');
// Class
class StarWars {
async getPerson(id) {
const response = await fetch(`http://swapi.co/api/people/${id}`);
return await response.json();
}
}
// IIFE - Immediately-Invoked Function Expression
(async () => {
const sw = new StarWars();
const person = await sw.getPerson(1);
console.log(person.name);
})();

Executando o código, ainda temos o mesmo resultado.

$ node sample.js
Luke Skywalker

Exportando uma classe com funções assíncronas

Para os 03 próximos exemplos, vamos escrever a classe StarWars em um arquivo separado e vamos adicionar uma nova função para entender como trabalhar com múltiplas requisições.

// star-wars.js
const fetch = require('node-fetch');
class StarWars {
async getPerson(id) {
const response = await fetch(`http://swapi.co/api/people/${id}`);
return await response.json();
}
async getFilm(id) {
const response = await fetch(`http://swapi.co/api/films/${id}`);
return await response.json();
}
}
module.exports = StarWars;

No código acima, criamos e exportamos a classe StarWars com duas funções assíncronas: getPerson() e getFilm().

Aguardando múltiplas requisições em sequência

Podemos utilizar async/await para trabalhar com múltiplas requisições em sequência.

const StarWars = require('./star-wars');
async function loadData() {
const sw = new StarWars();
const person = await sw.getPerson(1);
const film = await sw.getFilm(1);
console.log(person.name);
console.log(film.title);
}
loadData();

Neste exemplo, loadData() chama a função getPerson() e aguarda o resultado. Após o retorno do resultado, a função chama getFilm() e aguarda o resultado. Só após o segundo resultado é que os dados serão exibidos. Se cada uma das funções, getPerson() e getFilm(), demorassem 01 segundo para responder, os dados seriam exibidos após 02 segundos.

Executando o código, temos o seguinte resultado.

$ node sample.js
Luke Skywalker
A New Hope

Aguardando múltiplas requisições simultâneas

Podemos utilizar async/await para trabalhar com múltiplas requisições ao mesmo tempo.

const StarWars = require('./star-wars');
async function loadData() {
const sw = new StarWars();
const personPromise = sw.getPerson(1);
const filmPromise = sw.getFilm(1);
const person = await personPromise;
const film = await filmPromise;
console.log(person.name);
console.log(film.title);
}
loadData();

Neste exemplo, loadData() chama simultaneamente as funções getPerson() e getFilm() e armazena as chamadas respectivamente em personPromise e filmPromise. Reparem que não foi utilizado await nas chamadas dessas funções, ele foi utilizado apenas no retorno dos resultados para as variáveis person e film. Os dados só serão exibidos quando as duas chamadas retornarem. Se cada uma das funções, getPerson() e getFilm(), demorassem 01 segundo para responder, os dados seriam exibidos após 01 segundo.

Executando o código, ainda temos o mesmo resultado.

$ node sample.js
Luke Skywalker
A New Hope

Aguardando múltiplas requisições simultâneas usando Promise.all()

Neste último exemplo, veremos uma forma mais simples e mais elegante de aguardar múltiplas requisições simultâneas.

const StarWars = require('./star-wars');
async function loadData() {
const sw = new StarWars();
const [person, film] = await Promise.all([sw.getPerson(1), sw.getFilm(1)]);
console.log(person.name);
console.log(film.title);
}
loadData();

O código acima aguarda o retorno das duas funções simultaneamente utilizando Promise.all(). Os resultados das funções getPerson() e getFilm() serão armazenados respectivamente em person e film utilizando destructuring, uma nova funcionalidade do ES6. Os dados só serão exibidos após o retorno das duas funções.

Executando o código, ainda temos o mesmo resultado.

$ node sample.js
Luke Skywalker
A New Hope

Conclusão

Essa nova funcionalidade traz inúmeros benefícios quando trabalhamos com funções assíncronas. Espero ter conseguido esclarecer todas as dúvidas com relação à async/await.