Como usar async e await com vanilla JavaScript
doug-source
Posted on December 9, 2023
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();
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();
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();
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();
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();
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();
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();
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();
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();
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'
}));
};
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()
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();
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
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
December 12, 2023