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!