Como o Javascript pode te ajudar a cumprir promessas – parte 3

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:

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

https://www.promisejs.org/

Anúncios

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s