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. Caso conheça algum outro cenário onde possamos utilizar async/await, deixe nos comentários.
Vamos em frente!
“Talk is cheap. Show me the code.” – Linus Torvalds



