Na primeira e na segunda partes deste post, você já viu o que é e como usar uma promessa (Promise) em Javascript. Agora, que tal reescrever uma tarefa muito comum na forma de uma Promise? Vamos transformar uma requisição Ajax em uma promessa e usar algumas características mais recentes do Javascript para escrever um código ainda mais limpo.
Velhos hábitos, de um jeito novo
Um objeto XMLHttpRequest
já é assíncrono mas vamos usá-lo de um jeito diferente. Esse é o código inicial, bastante conhecido de quem trabalha com front end:
var req = new XMLHttpRequest(); req.open('GET', 'api.json'); req.onload = function() { // até um código 404 dispara este evento, por isso verificamos o status if (req.status == 200) { // OK, temos a resposta console.log(req.response); } else { // temos um código de erro console.log(req.statusText); } }; // tratar erros de rede req.onerror = function() { console.log('Erro de rede'); }; // Fazer a requisição req.send();
Agora com équio Promise:
function get(url) { // criar promessa return new Promise(function(resolve, reject) { // nada de novo aqui var req = new XMLHttpRequest(); req.open('GET', url); req.onload = function() { if (req.status == 200) { // Resolver a promessa com a resposta do servidor resolve(req.response); } else { // Rejeitar com a mensagem de erro reject(Error(req.statusText)); } }; req.onerror = function() { reject(Error('Erro de rede')); }; req.send(); }); } // tratar o resultado get('api.json') .then(function(resposta) { console.log('Sucesso: ', resposta); }) .catch(function(erro) { console.error('Erro: ', erro.message); });
Perceba: criei a função get para poder parametrizar a URL. A função retorna uma Promisse. Logo, o seu resultado é tratado com .then
e .catch
.
Só precisamos fazer a requisição dentro de uma Promise e usar os eventos do XMLHttpRequest
para definir se ela vai ser resolvida ou rejeitada.
Javascript moderno
ES5
A especificação ES5 (ECMAScript 5) do Javascript não suporta promises mas ela já é obsoleta e hoje os principais browsers, além do Node.js, já implementam especificações mais recentes.
ES6 / ES2015
O ECMAScript 2015 tem suporte total a promises e ainda acrescentou arrow functions e as palavras-chave let
e const
, já amplamente suportadas por todos os principais browsers. Vamos reescrever nossa promessa lá do primeiro post com este padrão:
const acabouCorrupcao = false; const oBrasilVaiMelhorar = new Promise ( (resolve, reject) => { if (acabouCorrupcao) { const resultado = { inflacao: 0, educacao: 10, corruptos: "presos" }; resolve(resultado); } else { const motivo = Error('Os corruptos ainda estão soltos! :('); reject(motivo); } } ); const protestar = function (motivo) { return Promise.reject(Error('Vamos protestar porque ' + motivo.message)); }; console.log('O Brasil vai melhorar!'); // promessa feita oBrasilVaiMelhorar .then(valor => { console.log('Agora vai! :)'); console.log(valor); }) .catch(protestar) .catch(motivo => { console.log('Só que não: ' + motivo.message); });
Trocamos o var
pelo const
e function(resolve, reject)
se tornou uma arrow function na forma (resolve, reject) =>
. Na verdade, qualquer função anônima pode ser definida dessa forma que lembra uma expressão lambda. Saiba mais:
- JavaScript ES6 Variable Declarations with let and const
- An introduction to Javascript ES6 arrow functions
ES7
O ES7 trouxe o conceito de funções assíncronas para um nível mais alto, facilitando ainda mais a escrita, a ponto de não parecer que o código é assíncrono. Também já suportado por todos os principais browsers. Essa é a versão ES7 do nosso código:
const acabouCorrupcao = false; const oBrasilVaiMelhorar = new Promise ( (resolve, reject) => { if (acabouCorrupcao) { var resultado = { inflacao: 0, educacao: 10, corruptos: "presos" }; resolve(resultado); } else { var motivo = Error('Os corruptos ainda estão soltos! :('); reject(motivo); } } ); async function protestar(motivo) { return Promise.resolve('Vamos protestar porque ' + motivo.message); }; async function fazerPromessa() { try { console.log('O Brasil vai melhorar!'); // promessa feita let resultado = await oBrasilVaiMelhorar; console.log('Agora vai! :)'); console.log(resultado); } catch (erro) { let protesto = await protestar(erro); console.log('Só que não! ' + protesto); } } (async () => { await fazerPromessa(); })();
Execute no CodePen.
O que mudou?
- Qualquer função que retorne um objeto Promise é declarada com
async
- Para chamar uma Promise, inclua
await
antes dela ou da chamada da função que a retorna - Criamos uma nova função
fazerPromessa
para que o código seja todo assícrono - O encadeamento de promessas é feito linearmente, como num código síncrono
- Para tratar uma promessa rejeitada, simplesmente use
try…catch
Por que e para que?
É importante saber escrever código assíncrono do jeito certo para não cair na armadilha de ter um código indecifrável, principalmente quando o sistema se tornar mais complexo. Além disso, inúmeras APIs Javascript existentes hoje usam promises e é essencial entender como elas funcionam para usá-las melhor.
Por exemplo, nos service workers todos os eventos são processados assincronamente.
Saiba mais
Este post foi extraído de uma seção do Curso Progressive Web Apps. Nele você vai aprender como construir uma aplicação web com vários recursos que antes só existiam em apps nativos. Entre para saber mais.
Quero criar meu primeiro app
Links úteis
https://github.com/domenic/promises-unwrapping/blob/master/docs/states-and-fates.md
https://developers.google.com/web/fundamentals/getting-started/primers/promises?hl=pt-br
https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Promise