Técnico

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.
[cc lang=”javascript” tab_size=”2″ theme=”blackboard” noborder=”true” line_numbers=”false”]
Async1(function () {
Async2(function () {
Async3(function () {
Async4(function () {
})
})
})
})
[/cc]
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:
[cc lang=”javascript” tab_size=”2″ theme=”blackboard” noborder=”true” line_numbers=”false”]
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);
});
[/cc]
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.
[cc lang=”javascript” tab_size=”2″ theme=”blackboard” noborder=”true” line_numbers=”false”]
Async1()
.then(Async2)
.then(Async3)
.then(Async4)
.catch(function(err){
console.error(err);
});
[/cc]
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()
[cc lang=”javascript” tab_size=”2″ theme=”blackboard” noborder=”true” line_numbers=”false”]
// 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);
});
[/cc]
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()
[cc lang=”javascript” tab_size=”2″ theme=”blackboard” noborder=”true” line_numbers=”false”]
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);
});
[/cc]
$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()
[cc lang=”javascript” tab_size=”2″ theme=”blackboard” noborder=”true” line_numbers=”false”]
$q.when([1,3,5,7,11,13])
.then(doSomething)
.catch(getErrors)
[/cc]
$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
[cc lang=”javascript” tab_size=”2″ theme=”blackboard” noborder=”true” line_numbers=”false”]
getPromise().then(function (promiseResult) {
getPromise2();
}).then(function (promise2Result) {
//Execute algo depois da promise2
});
[/cc]
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

[cc lang=”javascript” tab_size=”2″ theme=”blackboard” noborder=”true” line_numbers=”false”]
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
});
[/cc]

Como eu vou resolver promises dentro de um Loop?. Eu quero inativar todos os usuários baseado em um filtro.
[cc lang=”javascript” tab_size=”2″ theme=”blackboard” noborder=”true” line_numbers=”false”]
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
});
[/cc]
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.
[cc lang=”javascript” tab_size=”2″ theme=”blackboard” noborder=”true” line_numbers=”false”]
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.
});
[/cc]

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Compartilhe isso: