Async Await
Conheça tudo sobre a mais nova funcionalidade disponível no JavaScript para trabalhar com funções assíncronas.
- 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.jsLuke 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.
// Promisefunction getPerson(id) {...}// async/awaitasync function getPerson(id) {...}
O próximo passo é utilizar await
para cada processamento assíncrono dentro da função.
// Promisefetch(`http://swapi.co/api/people/${id}`).then((response) => response.json()).then((person) => console.log(person.name));// async/awaitconst 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.jsLuke 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.jsLuke 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.jsLuke 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 = 0getPerson(0).then((person) => console.log(person.name));
Executando o código, temos o seguinte resultado.
$ node sample.jsundefined
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 = 0getPerson(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.jsNot foundLuke 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 functionconst 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.jsLuke Skywalker
Podemos utilizar arrow functions
também.
const fetch = require('node-fetch');// arrow functionconst 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.jsLuke 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.jsconst person = await getPerson(1);^^^^^^^SyntaxError: Unexpected identifier
Await
só pode ser utilizado dentro de funções comAsync
.
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.jsLuke 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');// Classclass 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.jsLuke 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.jsconst 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.jsLuke SkywalkerA 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.jsLuke SkywalkerA 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.jsLuke SkywalkerA 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
.