Essa não será uma resposta completa para sua pergunta, mas espero que isso ajude você e outras pessoas quando tentar ler a documentação no $q
serviço. Demorei um pouco para entender.
Vamos anular o AngularJS por um momento e considerar as chamadas da API do Facebook. As duas chamadas de API usam um mecanismo de retorno de chamada para notificar o chamador quando a resposta do Facebook estiver disponível:
facebook.FB.api('/' + item, function (result) {
if (result.error) {
// handle error
} else {
// handle success
}
});
// program continues while request is pending
...
Este é um padrão padrão para lidar com operações assíncronas em JavaScript e outros idiomas.
Um grande problema com esse padrão surge quando você precisa executar uma sequência de operações assíncronas, em que cada operação sucessiva depende do resultado da operação anterior. É isso que este código está fazendo:
FB.login(function(response) {
if (response.authResponse) {
FB.api('/me', success);
} else {
fail('User cancelled login or did not fully authorize.');
}
});
Primeiro, ele tenta efetuar login e, somente depois de verificar se o logon foi bem-sucedido, faz a solicitação à API do Graph.
Mesmo neste caso, que está encadeando apenas duas operações, as coisas começam a ficar confusas. O método askFacebookForAuthentication
aceita um retorno de chamada por falha e sucesso, mas o que acontece quando FB.login
é bem-sucedido, mas FB.api
falha? Este método sempre chama o success
retorno de chamada, independentemente do resultado do FB.api
método.
Agora imagine que você está tentando codificar uma sequência robusta de três ou mais operações assíncronas, de maneira que lide adequadamente com os erros a cada etapa e seja legível para qualquer outra pessoa ou mesmo para você depois de algumas semanas. Possível, mas é muito fácil continuar aninhando esses retornos de chamada e perder o controle de erros ao longo do caminho.
Agora, vamos anular a API do Facebook por um momento e considerar a API de Promessas Angulares, conforme implementada pelo $q
serviço. O padrão implementado por este serviço é uma tentativa de transformar a programação assíncrona novamente em algo semelhante a uma série linear de instruções simples, com a capacidade de 'lançar' um erro em qualquer etapa do processo e manipulá-lo no final, semanticamente semelhante ao try/catch
bloco familiar .
Considere este exemplo artificial. Digamos que temos duas funções, em que a segunda função consome o resultado da primeira:
var firstFn = function(param) {
// do something with param
return 'firstResult';
};
var secondFn = function(param) {
// do something with param
return 'secondResult';
};
secondFn(firstFn());
Agora imagine que firstFn e secondFn demoram muito tempo para serem concluídos, portanto, queremos processar essa sequência de forma assíncrona. Primeiro, criamos um novo deferred
objeto, que representa uma cadeia de operações:
var deferred = $q.defer();
var promise = deferred.promise;
A promise
propriedade representa o resultado final da cadeia. Se você registrar uma promessa imediatamente após criá-la, verá que é apenas um objeto vazio ( {}
). Nada a ver ainda, siga em frente.
Até agora, nossa promessa representa apenas o ponto de partida da cadeia. Agora vamos adicionar nossas duas operações:
promise = promise.then(firstFn).then(secondFn);
O then
método adiciona uma etapa à cadeia e, em seguida, retorna uma nova promessa que representa o resultado final da cadeia estendida. Você pode adicionar quantas etapas desejar.
Até agora, configuramos nossa cadeia de funções, mas nada realmente aconteceu. Você começa chamando deferred.resolve
, especificando o valor inicial que deseja passar para a primeira etapa real da cadeia:
deferred.resolve('initial value');
E então ... ainda nada acontece. Para garantir que as alterações do modelo sejam observadas corretamente, o Angular não chama realmente a primeira etapa da cadeia até que a próxima vez $apply
seja chamada:
deferred.resolve('initial value');
$rootScope.$apply();
// or
$rootScope.$apply(function() {
deferred.resolve('initial value');
});
E o tratamento de erros? Até agora, especificamos apenas um manipulador de sucesso em cada etapa da cadeia. then
também aceita um manipulador de erros como um segundo argumento opcional. Aqui está outro exemplo mais longo de uma cadeia de promessas, desta vez com tratamento de erros:
var firstFn = function(param) {
// do something with param
if (param == 'bad value') {
return $q.reject('invalid value');
} else {
return 'firstResult';
}
};
var secondFn = function(param) {
// do something with param
if (param == 'bad value') {
return $q.reject('invalid value');
} else {
return 'secondResult';
}
};
var thirdFn = function(param) {
// do something with param
return 'thirdResult';
};
var errorFn = function(message) {
// handle error
};
var deferred = $q.defer();
var promise = deferred.promise.then(firstFn).then(secondFn).then(thirdFn, errorFn);
Como você pode ver neste exemplo, cada manipulador da cadeia tem a oportunidade de desviar o tráfego para o próximo manipulador de erros em vez do próximo manipulador de sucesso . Na maioria dos casos, você pode ter um único manipulador de erros no final da cadeia, mas também pode ter manipuladores de erros intermediários que tentam recuperar.
Para retornar rapidamente aos seus exemplos (e às suas perguntas), vou apenas dizer que eles representam duas maneiras diferentes de adaptar a API orientada a retorno de chamada do Facebook à maneira da Angular de observar alterações de modelo. O primeiro exemplo envolve a chamada da API em uma promessa, que pode ser adicionada a um escopo e é entendida pelo sistema de modelagem da Angular. O segundo adota a abordagem de força bruta de definir o resultado do retorno de chamada diretamente no escopo e depois chamar $scope.$digest()
para tornar o Angular ciente da mudança de uma fonte externa.
Os dois exemplos não são diretamente comparáveis, porque o primeiro está faltando na etapa de login. No entanto, geralmente é desejável encapsular interações com APIs externas como esta em serviços separados e entregar os resultados aos controladores como promessas. Dessa forma, você pode manter seus controladores separados das preocupações externas e testá-los mais facilmente com serviços simulados.