As funções assíncronas permitem escrever código baseado em promessas 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 que você escreva código baseado em promessas como se fosse síncrono, mas sem bloquear a linha de execução principal. Elas tornam seu 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 forma não bloqueante até que a promessa seja resolvida. Se a promessa for cumprida, você
vai receber o valor de volta. Se a promessa for rejeitada, o valor rejeitado será gerado.
Suporte ao navegador
Exemplo: como 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 todos os callbacks foram removidos. Isso facilita a leitura, especialmente para quem não está familiarizado com promessas.
Valores de retorno assíncronos
As funções assíncronas sempre retornam uma promessa, seja com await
ou não. Essa
promessa é resolvida com o que a função assíncrona retorna ou rejeita com
o que a função assíncrona gera. 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 cumpre com "world"
.
async function foo() {
await wait(500);
throw Error('bar');
}
…chamar foo()
retorna uma promessa que rejects com Error('bar')
.
Exemplo: transmissão de uma resposta
O benefício das funções assíncronas aumenta em exemplos mais complexos. Digamos que você queira transmitir uma resposta enquanto sai dos blocos e retorna o tamanho final.
Confira com as 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);
});
});
}
Sou Jake Archibald, o "executor de promessas". Percebeu como estou chamando
processResult()
dentro de si mesmo para configurar um loop assíncrono? Escrever isso me fez
sentir muito inteligente. Mas, como a maioria dos códigos "inteligentes", você precisa olhar para ele por
muito tempo para descobrir o que ele está fazendo, como uma daquelas imagens de olho mágico dos
anos 90.
Vamos tentar de novo 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;
}
A "inteligência" desapareceu. O loop assíncrono que me deixava tão satisfeito é
substituído por um loop while confiável e chato. Muito melhor. No futuro, você vai ter
iteradores assíncronos,
que vão
substituir o loop while
por um loop for-of, deixando tudo ainda mais organizado.
Outra sintaxe de função assíncrona
Já mostramos async function() {}
, mas a palavra-chave async
pode ser
usada com outras 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 muito sequencial
Embora você esteja escrevendo um código que parece 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!';
}
A instrução acima leva 1.000 ms para ser concluída, 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 exemplo 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 fazer o registro deles o mais rápido possível, na ordem correta.
Respire fundo: confira como isso fica com as 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());
}
Isso mesmo, estou usando reduce
para encadear uma sequência de promessas. Eu sou tão
inteligente. Mas isso é um pouco tão inteligente de programação que você não precisa.
No entanto, ao converter o código acima em uma função assíncrona, é tentador usar muita sequência:
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); } }
reduce
"inteligente" é substituído por um loop for padrão, chato e legível.
Solução alternativa de suporte a navegadores: geradores
Se você estiver segmentando navegadores que oferecem suporte a geradores (incluindo a versão mais recente de todos os principais navegadores), é possível usar uma espécie de polifill para funções assíncronas.
O Babel vai fazer isso por você. Confira um exemplo usando o Babel REPL
- Observe como o código transpilado é semelhante. Essa transformação faz parte da padrão es2017 do Babel.
Recomendo a abordagem de transpilação, porque você pode desativá-la quando os navegadores de destino oferecerem suporte a funções assíncronas. No entanto, se você realmente não quiser usar um transpilador, use a polyfill do Babel 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, o funcionamento é o mesmo.
Solução alternativa: regenerador
Se você estiver segmentando navegadores mais antigos, o Babel também poderá transpilinar geradores, permitindo que você use funções assíncronas até o IE8. Para fazer isso, você precisa da predefinição es2017 do Babel e da predefinição es2015.
A saída não é tão bonita, então fique atento ao inchaço do código.
Async tudo!
Quando as funções assíncronas forem lançadas em todos os navegadores, use-as em todas as funções que retornam promessas. Elas não apenas deixam seu código mais organizado, mas também garantem que a função sempre retornará uma promessa.
Fiquei muito animado com as funções assíncronas em 2014, e é ótimo vê-las chegarem de verdade nos navegadores. Whoop!