Cuidar de código assíncro dessa maneira, é lindo!

Funções async/await do ES7

Elas são incríveis. São tão incríveis que eu gostaria que as leis fossem alteradas e eu pudesse casar com elas.

async com Promise

No artigo sobre promise no HTML5Rocks, o exemplo final, mostra como carregar um JSON chamado story.json, e em seguida, usa o mesmo para buscar outros arquivos JSONs para os capítulos da história, e no final, renderizar os capítulos conforme suas chamadas se finalizam.

O código se parece com esse:

function loadStory() {
return getJSON('story.json').then(function(story) {
addHtmlToPage(story.heading);
return story.chapterURLs.map(getJSON)
.reduce(function(chain, chapterPromise) {
return chain.then(function() {
return chapterPromise;
}).then(function(chapter) {
addHtmlToPage(chapter.html);
});
}, Promise.resolve());
}).then(function() {
addTextToPage("Sucesso!");
}).catch(function(err) {
addTextToPage("Ops, capítulo não disponível: " + err.message);
}).then(function() {
document.querySelector('.spinner').style.display = 'none';
});
}

Não é ruim, porém…

Podemos usar ES7 async

Com funções async (veja aqui a proposta completa da especificação), você pode esperar uma promise com await. Isso pausa a chamada de uma maneira que não bloqueia a interface do usuário, aguarda o chamado ser resolvido e retorna seu valor.

async function loadStory() {
try {
let story = await getJSON('story.json');
addHtmlToPage(story.heading);
for (let chapter of story.chapterURLs.map(getJSON)) {
addHtmlToPage((await chapter).html);
}
addTextToPage("Sucesso");
} catch (err) {
addTextToPage("Ops, capítulo não disponível: " + err.message);
}
document.querySelector('.spinner').style.display = 'none';
}

Se a promise for rejeitada, ela retorna um erro com o valor rejeitado, que pode ser tratado no bloco catch.

Edição: Originalmente eu usei await com uma arrow function, aparentemente, isso não é permitido, então, eu substitui isso por um loop for. Domenic me explicou todos os “porquês” que uma arrow function não pode ser usada com await.

A função loadStory retorna uma promise, então você pode usar ela em outras funções async.

(async function() {
await loadStory();
console.log("Capítulo carregado com sucesso!");
}());

Até o ES7 chegar

Você pode usar as funções async e outras novidades do ES6/ES7 hoje, usando um transpilador. Aliás, você também pode usar generators para criar algo semelhante ao async/await.

Você vai precisar de uma pequeno pedaço de código, semelhante à essa função spawn. Então, você poderá usar generators semelhante à async/await:

function loadStory() {
return spawn(function *() {
try {
let story = yield getJSON('story.json');
addHtmlToPage(story.heading);
for (let chapter of story.chapterURLs.map(getJSON)) {
addHtmlToPage((yield chapter).html);
}
addTextToPage("All done");
} catch (err) {
addTextToPage("Argh, broken: " + err.message);
}
document.querySelector('.spinner').style.display = 'none';
});
}

No exemplo acima, estou passando uma função generator (perceba o *) para a função spawn. A função spawn chama o .next() no generator, recebe uma promise na chamada do yield e aguarda até ele se resolvido, chamando .next() para retornar o resultado (ou .throw() se for rejeitado).

ES7 trás uma função spawn nativa em sua especificação, ficando ainda mais fácil de usar. Ter um padrão para simplificar código assíncrono dessa maneira, é incrível!


Leituras futura


Créditos