Espere que todas as promessas sejam resolvidas

107

Então, eu tenho uma situação em que tenho várias cadeias de promessa de comprimento desconhecido. Quero que alguma ação seja executada quando todos os CHAINS tiverem sido processados. É mesmo possível? Aqui está um exemplo:

app.controller('MainCtrl', function($scope, $q, $timeout) {
    var one = $q.defer();
    var two = $q.defer();
    var three = $q.defer();

    var all = $q.all([one.promise, two.promise, three.promise]);
    all.then(allSuccess);

    function success(data) {
        console.log(data);
        return data + "Chained";
    }

    function allSuccess(){
        console.log("ALL PROMISES RESOLVED")
    }

    one.promise.then(success).then(success);
    two.promise.then(success);
    three.promise.then(success).then(success).then(success);

    $timeout(function () {
        one.resolve("one done");
    }, Math.random() * 1000);

    $timeout(function () {
        two.resolve("two done");
    }, Math.random() * 1000);

    $timeout(function () {
        three.resolve("three done");
    }, Math.random() * 1000);
});

Neste exemplo, configurei um $q.all()para as promessas um, dois e três que serão resolvidas em algum momento aleatório. Em seguida, adiciono promessas às pontas de um e três. Eu quero allresolver quando todas as cadeias forem resolvidas. Aqui está a saída quando executo este código:

one done 
one doneChained
two done
three done
ALL PROMISES RESOLVED
three doneChained
three doneChainedChained 

Existe uma maneira de esperar que as cadeias sejam resolvidas?

Jensengar
fonte

Respostas:

161

Eu quero que tudo se resolva quando todas as cadeias forem resolvidas.

Claro, então basta passar a promessa de cada cadeia para o em all()vez das promessas iniciais:

$q.all([one.promise, two.promise, three.promise]).then(function() {
    console.log("ALL INITIAL PROMISES RESOLVED");
});

var onechain   = one.promise.then(success).then(success),
    twochain   = two.promise.then(success),
    threechain = three.promise.then(success).then(success).then(success);

$q.all([onechain, twochain, threechain]).then(function() {
    console.log("ALL PROMISES RESOLVED");
});
Bergi
fonte
2
Obrigado por confirmar meu pior medo. Agora eu tenho que encontrar uma maneira de conseguir a última promessa lol.
jensengar
Qual é o problema disso? Suas correntes são construídas dinamicamente?
Bergi
Exatamente meu problema. Estou tentando criar dinamicamente uma cadeia de promessa, mas quero fazer algo quando a (s) cadeia (s) forem concluídas.
jensengar
Você pode nos mostrar seu código (talvez fazer uma nova pergunta)? Existem itens anexados à cadeia depois de Q.allexecutada - caso contrário, deve ser trivial?
Bergi
Adoraria mostrar o código ... mas ainda não terminei de escrevê-lo, mas farei o possível para explicá-lo. Tenho uma lista de "ações" que precisam ser realizadas. Essas ações podem ter qualquer número de níveis de sub-ações associadas a elas. Quero ser capaz de fazer algo quando todas as ações e suas subações estiverem concluídas. Provavelmente haverá vários $q.alls, no entanto, quando eu iniciar o processo de resolução, nenhuma nova ação / promessa será encadeada.
jensengar
16

A resposta aceita está correta. Gostaria de dar um exemplo para elaborá-lo um pouco para quem não está familiarizado promise.

Exemplo:

No meu exemplo, preciso substituir os srcatributos das imgtags por diferentes URLs de espelho, se disponíveis, antes de renderizar o conteúdo.

var img_tags = content.querySelectorAll('img');

function checkMirrorAvailability(url) {

    // blah blah 

    return promise;
}

function changeSrc(success, y, response) {
    if (success === true) {
        img_tags[y].setAttribute('src', response.mirror_url);
    } 
    else {
        console.log('No mirrors for: ' + img_tags[y].getAttribute('src'));
    }
}

var promise_array = [];

for (var y = 0; y < img_tags.length; y++) {
    var img_src = img_tags[y].getAttribute('src');

    promise_array.push(
        checkMirrorAvailability(img_src)
        .then(

            // a callback function only accept ONE argument. 
            // Here, we use  `.bind` to pass additional arguments to the
            // callback function (changeSrc).

            // successCallback
            changeSrc.bind(null, true, y),
            // errorCallback
            changeSrc.bind(null, false, y)
        )
    );
}

$q.all(promise_array)
.then(
    function() {
        console.log('all promises have returned with either success or failure!');
        render(content);
    }
    // We don't need an errorCallback function here, because above we handled
    // all errors.
);

Explicação:

Dos documentos AngularJS :

O thenmétodo:

then (successCallback, errorCallback, NoticeCallback) - independentemente de quando a promessa foi ou será resolvida ou rejeitada, então chama um dos callbacks de sucesso ou erro de forma assíncrona assim que o resultado estiver disponível. Os callbacks são chamados com um único argumento : o resultado ou motivo da rejeição.

$ q.all (promessas)

Combina várias promessas em uma única promessa que é resolvida quando todas as promessas de entrada são resolvidas.

O promisesparam pode ser uma série de promessas.

Sobre bind(), mais informações aqui: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

Hieu
fonte
O thenmétodo de $q.allé fornecido uma matriz das promessas retornadas, de modo que você pode fazer um loop nessa matriz e chamar thencada item na matriz, em vez de chamar thenquando adiciona a promessa a promise_array.
nick
4

Recentemente tive esse problema, mas com um número desconhecido de promessas. Resolvido usando jQuery.map () .

function methodThatChainsPromises(args) {

    //var args = [
    //    'myArg1',
    //    'myArg2',
    //    'myArg3',
    //];

    var deferred = $q.defer();
    var chain = args.map(methodThatTakeArgAndReturnsPromise);

    $q.all(chain)
    .then(function () {
        $log.debug('All promises have been resolved.');
        deferred.resolve();
    })
    .catch(function () {
        $log.debug('One or more promises failed.');
        deferred.reject();
    });

    return deferred.promise;
}
SoniCue
fonte
Não é jQuery.map (), mas Array.prototype.map () ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ), mas essa abordagem funciona.
Anastasia
0

Há uma maneira. $q.all(...

Você pode verificar os itens abaixo:

http://jsfiddle.net/ThomasBurleson/QqKuk/

http://denisonluz.com/blog/index.php/2013/10/06/angularjs-returning-multiple-promises-at-once-with-q-all/

Nikola Yovchev
fonte
Isso requer que eu saiba o comprimento da minha corrente, certo? Quer dizer, se eu tivesse uma promessa de comprimento 10, eu teria que fazer $q.all([p1.then(..).then(...).then(...).then(...) ...]);certo?
jensengar
0

Você pode usar "await" em uma "função assíncrona" .

app.controller('MainCtrl', async function($scope, $q, $timeout) {
  ...
  var all = await $q.all([one.promise, two.promise, three.promise]); 
  ...
}

NOTA: Não tenho 100% de certeza de que você pode chamar uma função assíncrona de uma função não assíncrona e obter os resultados corretos.

Dito isto, isso nunca seria usado em um site. Mas para teste de carga / teste de integração ... talvez.

Código de exemplo:

async function waitForIt(printMe) {
  console.log(printMe);
  console.log("..."+await req());
  console.log("Legendary!")
}

function req() {
  
  var promise = new Promise(resolve => {
    setTimeout(() => {
      resolve("DARY!");
    }, 2000);
    
  });

    return promise;
}

waitForIt("Legen-Wait For It");

Flavouski
fonte