Funções assíncronas permitem escrever código baseado em promessa como se fosse síncrono.
Funções assíncronas são ativadas por padrão no Chrome, Edge, Firefox e Safari e, sinceramente, elas são maravilhosas. Eles permitem escrever código baseado em promessa como se fosse síncrono, mas sem bloquear a linha de execução principal. Eles tornam seu código assíncrono menos "inteligente" e mais legível.
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
na função. Quando você aplica await
a uma promessa, a função é pausada
de maneira sem bloqueio até que a promessa seja resolvida. Se a promessa for atendida, você receberá o valor de volta. Se a promessa for rejeitada, o valor rejeitado será lançado.
Suporte ao navegador
Exemplo: registrar uma busca
Digamos que você queira buscar um URL e registrar a resposta como texto. Veja como isso 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 todos os callbacks desaparecem. Isso facilita muito a leitura, especialmente para pessoas menos familiarizadas com promessas.
Valores de retorno assíncronos
Funções assíncronas sempre retornam uma promessa, independentemente de você usar 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 gerar. Então, 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 é cumprida com "world"
.
async function foo() {
await wait(500);
throw Error('bar');
}
Chamar foo()
retorna uma promessa que é rejeitada com Error('bar')
.
Exemplo: streaming de uma resposta
O benefício das funções assíncronas aumenta em exemplos mais complexos. Digamos que você queira transmitir uma resposta ao registrar os blocos e retornar o tamanho final.
Aqui está, 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);
});
});
}
Confira-me, Jake "portador de promessas" Archibald. Viu como estou chamando
processResult()
dentro de si mesmo para configurar um loop assíncrono? Escrever isso me fez
me sentir muito inteligente. Mas, como a maioria dos códigos "inteligentes", você tem que olhar para ele por um certo tempo para descobrir o que ele está fazendo, como uma daquelas imagens de olhos mágicos dos anos 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 é
substituído por um while-loop confiável e enfadonho. Muito melhor. No futuro, você terá
iteradores assíncronos
que substituiriam o loop while
por uma repetição for-of, tornando-o ainda mais elegante.
Outra sintaxe de função assíncrona
Já mostrei 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
Mesmo que você esteja escrevendo um código síncrono, não perca 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 exemplo acima leva 1.000 ms 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 processo acima leva 500 ms para ser concluído, porque as duas esperas acontecem 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 rápido possível, na ordem correta.
Respire 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 encadear uma sequência de promessas. Sou muito
inteligente. Mas com essa codificação tão inteligente, fica melhor sem.
No entanto, ao converter o item acima em 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 navegadores compatíveis: geradores
Se você está segmentando navegadores compatíveis com geradores (o que inclui a versão mais recente dos principais navegadores), você pode usar funções assíncronas de polyfill.
O Babel vai fazer isso por você. Confira um exemplo do Babel REPL
- observe como o código transcompilado é semelhante. Essa transformação faz parte da predefinição es2017 do Babel (link em inglês).
Recomendamos a abordagem de transcompilação, porque você poderá desativá-la quando seus navegadores de destino forem compatíveis com funções assíncronas, mas se você realmente não quiser usar um transcompilador, poderá usar o polyfill do Babel 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;
});
É necessário transmitir um gerador (function*
) para createAsyncFunction
e usar yield
em vez de await
. Fora isso, funciona da mesma forma.
Alternativa: regenerador
Se seu app for destinado a navegadores mais antigos, o Babel também poderá transcompilar geradores, permitindo que você use funções assíncronas até mesmo no IE8. Para fazer isso, você precisa da predefinição es2017 do Babel e da predefinição es2015.
A saída não é tão bela, então cuidado com sobrecarga de código.
Faça tudo assíncrono.
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 garantem que a função sempre retornará uma promessa.
Fiquei muito entusiasmado com as funções assíncronas em 2014 e é ótimo vê-las, de fato, nos navegadores. Ops!