AngularJS: $ q-> adiada ordem da ordem das coisas (ciclo de vida) E quem chama digest?

O serviço $ q é muito poderoso em angularjs e facilita nossa vida com código asynchronous.

Eu sou novo em angular mas usando API adiada não é muito novo para mim. Devo dizer que eu estou completamente certo com o How to use parte da documentação + há links muito úteis para isso dentro dos documentos + eu verifiquei a fonte também.

Minha pergunta é mais sobre as partes sob o capô de objects de API adiada e promissora em angular. Quais são as fases exatas em seus ciclos de vida e como elas interagem com rootScope.Scope (s). Minhas suposições são que quando a promise resolve – ela invoca o loop digest? sim não ?

Pode-se fornecer uma resposta detalhada com respeito específico à seguinte lista de aspectos:

  1. Qual é a ordem das coisas que acontecem em cada um dos seus passos / fases descritos?
  2. Quando um novo object adiado com uma nova instância de promise é criado – quem está ciente disso / é importante?
  3. Como exatamente o escopo foi atualizado quando o object promissor foi resolvido? Tenho que atualizá-lo manualmente dentro do callback ou o digest será invocado automaticamente e atualizar o rootScope como declarado aqui
  4. mencionar pelo menos uma abordagem de atualização do escopo de dentro do callback promissor
  5. Eu suponho que há muitos outros aspectos úteis, sinta-se à vontade para fornecer todos eles.

Aprecio e aceito a resposta mais detalhada, com o máximo de referências possíveis a documentos ou fonts (que não consegui encontrar por mim). Eu não consigo encontrar nenhuma discussão anterior com este tópico, se já houve – por favor postar links.

ps: +1 para qualquer um que ajude sugerindo um título melhor para essa pergunta, adicione suas sugestões em um comentário.

Felicidades!

Promessas têm três estados

  • Pendente – é assim que as promises começam.
  • Cumprido – é o que acontece quando você resolve um adiamento ou quando o valor de retorno de .then , e geralmente é análogo a um valor de retorno padrão.
  • Rejeitado – Isto é o que acontece quando você rejeita um diferido, quando você throw de um manipulador. Ou quando você retorna uma promise que se desfaz a uma rejeição *, é geralmente análogo a uma exceção padrão lançada.

Em Angular, as promises resolvem de forma assíncrona e fornecem suas garantias, resolvendo via $rootScope.$evalAsync(callback); (retirado daqui ).

Como ele é executado por meio do $evalAsync , sabemos que pelo menos um ciclo de $evalAsync ocorrerá após a promise ser resolvida (normalmente), já que ele programará um novo resumo se não estiver em andamento.

É também por isso que, por exemplo, quando você deseja testar código de teste unitário no Angular, é necessário executar um loop digest (geralmente, no rootScope via $rootScope.digest() ), pois a execução do $ evalAsync faz parte do loop digest.

Ok, chega de conversa, mostre-me o código:

Nota: Isso mostra os caminhos de código do Angular 1.2, os caminhos de código no Angular 1.x são todos semelhantes, mas em 1.3+ $ q foi refatorado para usar a inheritance prototípica, então essa resposta não é precisa no código (mas está no espírito) para essas versões.

1) Quando $ q é criado, isso acontece:

  this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { return qFactory(function(callback) { $rootScope.$evalAsync(callback); }, $exceptionHandler); }]; 

Que por sua vez, faz:

 function qFactory(nextTick, exceptionHandler) { 

E só resolve no nextTick passado como $evalAsync dentro resolve e notifica:

  resolve: function(val) { if (pending) { var callbacks = pending; pending = undefined; value = ref(val); if (callbacks.length) { nextTick(function() { var callback; for (var i = 0, ii = callbacks.length; i < ii; i++) { callback = callbacks[i]; value.then(callback[0], callback[1], callback[2]); } }); } } }, 

No escopo da raiz, $ evalAsync é definido como:

  $evalAsync: function(expr) { // if we are outside of an $digest loop and this is the first time we are scheduling async // task also schedule async auto-flush if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) { $browser.defer(function() { if ($rootScope.$$asyncQueue.length) { $rootScope.$digest(); } }); } this.$$asyncQueue.push({scope: this, expression: expr}); }, $$postDigest : function(fn) { this.$$postDigestQueue.push(fn); }, 

Que, como você pode ver, realmente programa um resumo se não estivermos em um e nenhum resumo tiver sido programado anteriormente. Em seguida, ele envia a function para o $$asyncQueue .

Por sua vez, dentro de $ digest (durante um ciclo e antes de testar os observadores):

  asyncQueue = this.$$asyncQueue, ... while(asyncQueue.length) { try { asyncTask = asyncQueue.shift(); asyncTask.scope.$eval(asyncTask.expression); } catch (e) { clearPhase(); $exceptionHandler(e); } lastDirtyWatch = null; } 

Então, como podemos ver, ele é executado no $$asyncQueue até que esteja vazio, executando o código na sua promise.

Então, como podemos ver, atualizar o escopo é simplesmente atribuir a ele, um resumo será executado se ainda não estiver em execução e, se estiver, o código dentro da promise, executado em $evalAsync será chamado antes dos observadores serem executados. Então, um simples:

 myPromise().then(function(result){ $scope.someName = result; }); 

Sufixo, mantendo-o simples.

* note que o angular distingue lances de rejeições - os lances são registrados por padrão e as rejeições devem ser registradas explicitamente

quando a promise resolve – ele invoca o loop digest?

Sim. Você pode testar isso por um modelo simples:

 {{state}} 

e código do controlador que altera a variável $scope.state após um atraso:

 $scope.state = 'Pending'; var d = $q.defer(); d.promise.then(function() { $scope.state = 'Resolved, and digest cycle must have run'; }); $window.setTimeout(function() { d.resolve(); }, 1000); 

Você pode ver isso em http://plnkr.co/edit/fIfHYz9EYK14A5OS6NLd?p=preview . Após um segundo, o texto no HTML mostra Resolved, and digest cycle must have run . A chamada para setTimeout vez de $timeout é deliberada, para assegurar que deve ser resolve que acaba iniciando o loop digest.

Isto é confirmado olhando no source: resolve chama seus callbacks via nextTick , que é uma function que passou os callbacks para $rootScope.$evalAsync , que de acordo com os documentos $evalAsync :

pelo menos um ciclo $ digest será executado após a execução da expressão

Esses mesmos documentos também dizem:

Nota: se esta function for chamada fora de um ciclo $ digest, um novo ciclo $ digest será agendado

assim, se a pilha já está no loop $ digest, pode alterar a ordem exata dos events.


‘1. Qual é a ordem das coisas que acontecem em cada um dos seus passos / fases descritos?

Indo para o exemplo anterior em detalhes:

  1. var d = $q.defer(); A promise do object adiado está em um estado pendente. Neste ponto, praticamente nada aconteceu, você apenas tem um object diferido com propriedades de resolve , reject , notifiy e promise . Nada usou ou afetou o loop $ digest

  2. d.promise.then(function() { $scope.state = 'Resolved, and digest cycle must have run'; });

    A promise ainda está em um estado pendente, mas com um callback de sucesso registrado. Novamente, nada usou ou afetou o loop $ digest ou qualquer escopo.

  3. $window.setTimeout(function() { d.resolve(); }, 1000);

    Após 1 segundo, d.resolve será chamado. Isso passa o retorno de chamada definido na etapa 2 acima para $ evalAsync (via nextTick) .

  4. $ evalAsync irá chamar o retorno de chamada

  5. $ evalAsync irá garantir que um ciclo $ digest seja chamado.


2 Quando um novo object adiado com uma nova instância de promise é criado – quem está ciente disso / é importante?

Apenas o chamador de $q.defer() . Nada acontece com relação a qualquer escopo até que seja resolved chamado (ou, na verdade, reject ou notify ).


‘3. Como exatamente o escopo atualizado promete o object sendo resolvido? Tenho que atualizá-lo manualmente dentro do callback ou o digest será invocado automaticamente e atualizar o rootScope como declarado aqui

Como mencionado, o loop $ digest será iniciado automaticamente chamando resolve (se não estiver nele).


4. mencionar pelo menos uma abordagem de atualização do escopo de dentro do callback promissor

O exemplo acima dá isso.


5 Eu suponho que há muitos outros aspectos úteis, sinta-se à vontade para fornecer todos eles.

Não que eu possa pensar!