25 fev 2016
AngularJS

Trabalhando com Promises no AngularJS

Promises foram criadas para lidar com um problema antigo de linguagens assíncronas, as callbacks, mais especificamente as callback hells.

Async1(function () {
  Async2(function () {
    Async3(function () {
      Async4(function () {
      })
    })
  })
})

Todas essas funções anônimas tornam um inferno a leitura, entendimento e tratamento de possíveis erros, é ai que entram as promises.

Uma promise é um objeto que possui três estados determinados(pendente, realizada ou rejeitado) e implementa os métodos .then() e .catch().

.then([func]onResolve, [func]onReject): que recebe dois argumentos, onResolve sendo a função a ser executada quando a promise for resolvida com sucesso e onReject caso ela seja rejeitada.

.catch([func]onReject): que não passa de um alias para .then(null, onReject).

O método then() deve sempre retornar uma promise, possibilitando assim o encadeamento de chamadas sobre a mesma promise.

Assumindo que todas as funções AsyncX retornem uma promise:

Async1()
  .then(function(Async1Resp){
    return Async2(Async1Resp);
  })
  .then(function(Async2Resp){
    return Async3(Async2Resp);
  })
  .then(function(Async3Resp){
    return Async4(Async3Resp);
  })
  .catch(function(err){
    console.error(err);
  });

Desta forma fica muito mais clara a ordem de execução de nossas chamadas assíncronas, mas como AsyncX retorna uma Promise podemos ir mais além e simplificar mais ainda as coisas.

Async1()
  .then(Async2)
  .then(Async3)
  .then(Async4)
  .catch(function(err){
    console.error(err);
  });

Agora sim podemos ver o verdadeiro benefício das promises, como o método .catch() está em último na cadeia, pegará qualquer eventual erro que ocorra em qualquer uma das promises anteriores.

Tudo bem, mas como eu crio uma promise em Angular? É ai que entra o serviço $q. nele existem três formas de se criar uma promise, vamos ver agora como utilizalos e o mais importante, quando não utilizalos.

$q()

// Código retirado da documentação do Angular
// https://docs.angularjs.org/api/ng/service/$q
function asyncGreet(name) {
  return $q(function(resolve, reject) {
    setTimeout(function() {
      if (okToGreet(name)) {
        resolve('Hello, ' + name + '!');
      } else {
        reject('Greeting ' + name + ' is not allowed.');
      }
    }, 1000);
  });
}

var promise = asyncGreet('Robin Hood');
promise.then(function(greeting) {
  alert('Success: ' + greeting);
}, function(reason) {
  alert('Failed: ' + reason);
});

Podemos ver aqui que asyncGreet retorna o resultado de $q que é uma promise. $q() recebe uma função como argumento, essa função recebe por sua vez dois argumentos, resolve e reject, são eles os responsáveis por resolverem ou rejeitarem o retorno de $q

$q.defer()

function asyncGreet(name) {
  var deferred = $q.defer();

  setTimeout(function() {
    if (okToGreet(name)) {
      deferred.resolve('Hello, ' + name + '!');
    } else {
      deferred.reject('Greeting ' + name + ' is not allowed.');
    }
  }, 1000);

  return deferred.promise;
}

var promise = asyncGreet('Robin Hood');
  promise.then(function(greeting) {
alert('Success: ' + greeting);
}, function(reason) {
  alert('Failed: ' + reason);
});

$q.defer() retorna um DeferredObject que contem a promise e os métodos deferred.resolve() e deferred.reject() responsáveis por resolver ou rejeitar a promise.

$q.when()

$q.when([1,3,5,7,11,13])
  .then(doSomething)
  .catch(getErrors)

$q.when() pode receber qualquer objeto, e devolve uma promise, se ele for uma promise(possuir o método .then()) ele irá encapsular essa promise dentro dos padrões do $q, se for qualquer outra coisa, retornará uma promise já resolvida.
Quando devo utilizar $q.when()? Sempre que você não puder confiar em algum retorno, se uma função puder retornar um valor ou uma promise, encapsule o mesmo em .when() assim você terá garantia de que a partir deste ponto você terá uma promise confiável.

Quando devo utilizar $q() e $q.defer()? Somente quando estiver lidando com processos assíncronos que ainda não são promises, tudo que é assíncrono no Angular já é uma promise $http, $timeout, mas caso esteja encapsulando um plugin de JQuery, ou lidando com qualquer outro processo que dependa de callbacks, o ideal é utilizar um dos dois para manter a consistência e legibilidade no seu código. Ex.: Serviço UploadResize do plugin ngFileUpload

Erros comuns com promises

Quebrando a cadeia de promises

getPromise().then(function (promiseResult) {
  getPromise2();
}).then(function (promise2Result) {
  //Execute algo depois da promise2
});

Isso esta fundamentalmente errado por um simples motivo, getPromise2 é um processo assíncrono, sendo assim, o processo irá iniciar e o bloco then() irá retornar undefined, o segundo bloco irá receber undefined como argumento e irá processar ao mesmo tempo de getPromise2

getPromise().then(function (promiseResult) {
  return getPromise2();
}).then(function (promise2Result) {
  //Agora sim o bloco será executado após getPromise2 ser resolvida
  //promise2Result irá conter o valor desta resolução
});

Como eu vou resolver promises dentro de um Loop?. Eu quero inativar todos os usuários baseado em um filtro.

User.getAll(filterObj).then(function (result) {
  result.users.forEach(function (user) {
    User.disable(user.id);
  });
}).then(function () {
  // Essa função será executada exatamente após o fim do then() anterior
});

Mais uma vez você quebrou a cadeia de promises, todas as promises foram disparadas porem não existe nada indicando que se deve esperar antes de executar o segundo .then(), e qualquer erro que ocorra em algum dos User.disable() não será tratado corretamente. Oque você esta procurando é o método $q.all(), .all([] ou {}) recebe um array ou hash de promises como argumento e retorna uma promise que somente será resolvida quando todas as promises no array ou hash forem resolvidas. Caso alguma promise falhe, essa promise será rejeitada com a mesma rejeição da promise rejeitada.

User.getAll(filterObj).then(function (result) {
  return result.users.map(function (user) {
    return User.disable(user.id);
  });
}).then(function (promisesResults) {
  // Essa função será executada somente quando todas as
  // promises forem resolvidas
}).catch(function(err){
  // Se ocorrer algum erro em qualquer uma das promises
  // o erro poderá ser tratado aqui.
});

Comments

  • Jonatas Cunha
    fevereiro 6, 2017 Responder

    Parabéns, pelo artigo, me ajudou bastante.

Leave a Comment