As funções assíncronas permitem escrever código baseado em promessa como se fosse síncrono.
As funções assíncronas são ativadas por padrão no Chrome, Edge, Firefox e Safari, e são, francamente, maravilhosas. Elas permitem escrever código baseado em promessa como se fosse síncrono, mas sem bloquear a linha de execução principal. Elas tornam o código assíncrono menos "inteligente" e mais legível.
As funções assíncronas funcionam assim:
async function myFirstAsyncFunction() {
try {
const fulfilledValue = await promise;
} catch (rejectedValue) {
// …
}
}
Se você usar a palavra-chave async
antes de uma definição de função, poderá usar
await
dentro da função. Quando você await
uma promessa, a função é pausada
de uma forma não-bloqueante até que a promessa seja concluída. Se a promessa for cumprida, você
vai receber o valor de volta. Se a promessa for rejeitada, o valor rejeitado será descartado.
Suporte ao navegador
Exemplo: registrar uma busca
Digamos que você queira buscar um URL e registrar a resposta como texto. Confira como fica usando promessas:
function logFetch(url) {
return fetch(url)
.then((response) => response.text())
.then((text) => {
console.log(text);
})
.catch((err) => {
console.error('fetch failed', err);
});
}
E aqui está a mesma coisa usando funções assíncronas:
async function logFetch(url) {
try {
const response = await fetch(url);
console.log(await response.text());
} catch (err) {
console.log('fetch failed', err);
}
}
É o mesmo número de linhas, mas todas as chamadas de retorno desapareceram. Isso facilita muito a leitura, especialmente para pessoas menos familiarizadas com promessas.
Valores de retorno assíncronos
As funções assíncronas sempre retornam uma promessa, quer você use await
ou não. Essa
promessa é resolvida com qualquer coisa que a função assíncrona retorne, ou rejeitada com
qualquer coisa que a função assíncrona descarte. Assim, com:
// wait ms milliseconds
function wait(ms) {
return new Promise((r) => setTimeout(r, ms));
}
async function hello() {
await wait(500);
return 'world';
}
... chamar hello()
retorna uma promessa que se cumpre com "world"
.
async function foo() {
await wait(500);
throw Error('bar');
}
…chamar foo()
retorna uma promessa que rejeita com Error('bar')
.
Exemplo: streaming de uma resposta
A vantagem das funções assíncronas aumenta em exemplos mais complexos. Digamos que você queira transmitir uma resposta ao registrar as partes e retornar o tamanho final.
Aqui está ela com promessas:
function getResponseSize(url) {
return fetch(url).then((response) => {
const reader = response.body.getReader();
let total = 0;
return reader.read().then(function processResult(result) {
if (result.done) return total;
const value = result.value;
total += value.length;
console.log('Received chunk', value);
return reader.read().then(processResult);
});
});
}
Verifique-me, Jake "portador de promessas" Archibald. Vê como estou chamando
processResult()
dentro de si para configurar um loop assíncrono? Escrever isso me fez
sentir muito inteligente. Porém, como com a maioria dos códigos "inteligentes", você tem que analisá-lo
por um longo tempo para descobrir o que está fazendo, como uma daquelas imagens de olho-mágico da
década de 90.
Vamos tentar novamente com funções assíncronas:
async function getResponseSize(url) {
const response = await fetch(url);
const reader = response.body.getReader();
let result = await reader.read();
let total = 0;
while (!result.done) {
const value = result.value;
total += value.length;
console.log('Received chunk', value);
// get the next result
result = await reader.read();
}
return total;
}
Todo o "inteligente" desapareceu. O loop assíncrono que me fez sentir tão presunçoso foi
substituído por um "while-loop" confiável e entediante. Muito melhor. No futuro, você terá iteradores assíncronos, que substituirão o loop while
por um loop "for-of", tornando-o ainda mais elegante.
Outra sintaxe de função assíncrona
Já mostramos async function() {}
, mas a palavra-chave async
pode ser
usada com outra sintaxe de função:
Funções de seta
// map some URLs to json-promises
const jsonPromises = urls.map(async (url) => {
const response = await fetch(url);
return response.json();
});
Métodos de objeto
const storage = {
async getAvatar(name) {
const cache = await caches.open('avatars');
return cache.match(`/avatars/${name}.jpg`);
}
};
storage.getAvatar('jaffathecake').then(…);
Métodos de classe
class Storage {
constructor() {
this.cachePromise = caches.open('avatars');
}
async getAvatar(name) {
const cache = await this.cachePromise;
return cache.match(`/avatars/${name}.jpg`);
}
}
const storage = new Storage();
storage.getAvatar('jaffathecake').then(…);
Tenha cuidado! Evite ser sequencial demais
Embora esteja compilando código síncrono, certifique-se de não perder a oportunidade de fazer coisas em paralelo.
async function series() {
await wait(500); // Wait 500ms…
await wait(500); // …then wait another 500ms.
return 'done!';
}
O item acima leva 1000ms para ser concluído, enquanto:
async function parallel() {
const wait1 = wait(500); // Start a 500ms timer asynchronously…
const wait2 = wait(500); // …meaning this timer happens in parallel.
await Promise.all([wait1, wait2]); // Wait for both timers in parallel.
return 'done!';
}
O item acima leva 500 ms para ser concluído, porque ambas as esperas ocorrem ao mesmo tempo. Vamos conferir um exemplo prático.
Exemplo: como gerar buscas em ordem
Digamos que você queira buscar uma série de URLs e registrá-los o mais rapidamente possível, na ordem correta.
Respira fundo: eis como isso fica com promessas:
function markHandled(promise) {
promise.catch(() => {});
return promise;
}
function logInOrder(urls) {
// fetch all the URLs
const textPromises = urls.map((url) => {
return markHandled(fetch(url).then((response) => response.text()));
});
// log them in order
return textPromises.reduce((chain, textPromise) => {
return chain.then(() => textPromise).then((text) => console.log(text));
}, Promise.resolve());
}
Sim, isso mesmo, estou usando reduce
para ligar de uma sequência de promessas. Eu sou tão
inteligente. Mas isso é uma codificação muito inteligente que é melhor evitar.
No entanto, ao converter o item acima para uma função assíncrona, é tentador ser sequencial demais:
async function logInOrder(urls) {
for (const url of urls) {
const response = await fetch(url);
console.log(await response.text());
}
}
function markHandled(...promises) {
Promise.allSettled(promises);
}
async function logInOrder(urls) {
// fetch all the URLs in parallel
const textPromises = urls.map(async (url) => {
const response = await fetch(url);
return response.text();
});
markHandled(...textPromises);
// log them in sequence
for (const textPromise of textPromises) {
console.log(await textPromise);
}
}
Solução alternativa para suporte do navegador: geradores
Se você tem como alvo navegadores que suportam geradores (que inclui a versão mais recente de todos os principais navegadores ), você pode usar funções assíncronas polyfill.
O Babel vai fazer isso por você. Confira um exemplo usando o Babel REPL
- observe quão semelhante é o código transcompilado. Essa transformação faz parte da pré-configuração es2017 do Babel.
Recomendo a abordagem de transcompilação, porque você pode desativá-la quando os navegadores de destino oferecerem suporte a funções assíncronas, mas se você realmente não quiser usar um transpilador, poderá usar o polyfill do Babel (em inglês) e usá-lo por conta própria. Em vez de:
async function slowEcho(val) {
await wait(1000);
return val;
}
…você incluiria o polyfill e escreveria:
const slowEcho = createAsyncFunction(function* (val) {
yield wait(1000);
return val;
});
Você precisa transmitir um gerador (function*
) para createAsyncFunction
e usar yield
em vez de await
. Exceto por isso, funciona da mesma forma.
Solução alternativa: regenerador
Se seu objetivo são navegadores mais antigos, o Babel também pode transcompilar geradores, o que permite usar funções assíncronas até mesmo no IE8. Para fazer isso, você precisa da pré-configuração es2017 do Babel e da pré-configuração es2015.
A saída não é tão bonita, então cuidado com o excesso de código.
Sincronize tudo!
Uma vez que funções assíncronas funcionam em todos os navegadores, use-as em todas as funções que retornam promessas. Elas não apenas tornam seu código mais organizado, mas também garante que a função sempre retorne uma promessa.
Fiquei muito animado com as funções assíncronas em 2014, e é ótimo vê-las chegando, de verdade, nos navegadores. Legal!