2 votes

Comment puis-je renvoyer une promesse à partir d'une requête récursive et la résoudre lorsque les données correspondent à une condition ?

Je consomme une API qui renvoie du JSON, et sur cette page, j'ai une barre de progression indiquant les différentes étapes de la mise en place de quelque chose à la demande de l'utilisateur. Chaque callback de réussite d'une requête AJAX déclenche la requête suivante, car elles doivent être effectuées en séquence. Une étape lance une tâche d'arrière-plan côté serveur et le point de terminaison renvoie un ID de transaction.

En dehors de ce flux, il existe une fonction qui vérifie un autre point de terminaison pour voir si cette transaction est terminée ou non. Si elle est "en attente", je dois réémettre la demande après un petit délai.

Je l'ai fait fonctionner avec une fonction récursive :

function checkTransaction(trxid) {
    window.profileTrx[trxid] = 0;
    trxurl = 'https://example.com/transaction/'+trxid;
    $.getJSON(trxurl,function(result) {
        if(result.status === 'pending') {
            setTimeout(function () {
                checkTransaction(trxid);
            },3000);
        } else {
            window.profileTrx[trxid] = result;
        }
    });
}

La raison pour laquelle j'utilisais window est que je pouvais accéder à la transaction par son ID dans le callback d'où elle provenait - un bon cas d'utilisation pour une promesse s'il en existe un. Mais c'est devenu compliqué, et mon manque d'expérience a commencé à me gêner. En bouclant sur l'état de window.profileTrx[trxid] semblait être un travail double, et ne se comportait pas comme prévu, bouclant trop rapidement et faisant planter la page. Encore une fois, une promesse avec l'étape suivante dans .then() était mon idée, mais je n'arrive pas à trouver comment.

Comment pourrais-je implémenter cela avec des promesses de sorte que la fonction de rappel qui a initié le "contrôle de transaction" récursif ne poursuive le reste de son exécution qu'une fois que l'API renvoie une réponse non suspensive au contrôle ?

Je pouvais me faire à l'idée de récurer et de rendre une promesse, mais pas les deux à la fois. Toute aide est la bienvenue.

3voto

danh Points 21498

J'ai toujours l'esprit plus clair lorsque je tiens compte des promesses d'abord :

// wrap timeout in a promise
function wait(ms) {
    var deferred = $.Deferred();
    setTimeout(function() {
        deferred.resolve();
    }, ms);
    return deferred.promise();
}

// promise to get a trxid
function getTRX(trxid) {
    var trxurl = 'https://example.com/transaction/'+trxid;
    return $.getJSON(trxurl);
}

Maintenant la fonction originale semble facile...

function checkTransaction(trxid) {
    window.profileTrx[trxid] = trxid;
    return getTRX(trxid).then(function(result) {
        if (result.status === 'pending') {
            return wait(3000).then(function() {
                return checkTransaction(trioxid);
            });
        } else {
            window.profileTrx[trxid] = result;
            return result;
        }
    });
}

L'appelant ressemblera à ceci :

return checkTransaction('id0').then(function(result) {
    return checkTransaction('id1');
}).then(function(result) {
    return checkTransaction('id2');
}) // etc

N'oubliez pas que si le checkTransaction reste en attente pendant très longtemps, vous allez construire de très longues chaînes de promesses. Assurez-vous que le get retourne dans un très petit multiple de 3000ms.

1voto

Michael Bromley Points 774

Solution basée sur le "différé" (non recommandé)

Puisque vous utilisez jQuery dans votre question, je vais d'abord vous présenter une solution qui utilise l'implémentation de la promesse de jQuery basée sur la fonction $.Deferred() objet. Comme l'a souligné @Bergi, c'est considéré comme un anti-modèle .

// for this demo, we will fake the fact that the result comes back positive
// after the third attempt.
var attempts = 0;

function checkTransaction(trxid) {
  var deferred = $.Deferred();
  var trxurl = 'http://echo.jsontest.com/status/pending?' + trxid;

  function poll() {
    console.log('polling...');
    // Just for the demo, we mock a different response after 2 attempts.
    if (attempts == 2) {
      trxurl = 'http://echo.jsontest.com/status/done?' + trxid;
    }

    $.getJSON(trxurl, function(result) {
      if (result.status === 'pending') {
        console.log('result:', result);
        setTimeout(poll, 3000);
      } else {
        deferred.resolve('final value!');
      }
    });

    // just for this demo
    attempts++;
  }

  poll();

  return deferred.promise();
}

checkTransaction(1).then(function(result) {
  console.log('done:', result)
});

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Cela devrait fonctionner (exécutez l'extrait pour voir), mais comme mentionné dans la réponse liée, il y a des problèmes avec ce modèle "différé", comme les cas d'erreur qui ne sont pas signalés.

Le problème est que jQuery promet (jusqu'à des versions peut-être récentes - je n'ai pas vérifié) ont des problèmes massifs qui empêchent l'utilisation de meilleurs modèles .

Une autre approche consisterait à utiliser une bibliothèque dédiée aux promesses, qui implémente un chaînage correct sur les éléments suivants then() afin que vous puissiez composer votre fonction d'une manière plus robuste et éviter l'anti-modèle "différé" :

Solution de composition prometteuse (meilleure)

Pour une véritable composition de promesses, qui évite complètement l'utilisation d'objets "différés", nous pouvons utiliser une bibliothèque de promesses plus conforme, telle que Oiseau bleu . Dans le snippet ci-dessous, j'utilise Bluebird, qui nous donne une Promise qui fonctionne comme prévu.

function checkTransaction(trxid) {
  var trxurl = 'http://echo.jsontest.com/status/pending?' + trxid;

  var attempts = 0;

  function poll() {
    if (attempts == 2) {
      trxurl = 'http://echo.jsontest.com/status/done?' + trxid;
    }
    attempts++;
    console.log('polling...');

    // wrap jQuery's .getJSON in a Bluebird promise so that we
    // can chain & compose .then() calls.
    return Promise.resolve($.getJSON(trxurl)
      .then(function(result) {
        console.log('result:', result);
        if (result.status === 'pending') {

          // Bluebird has a built-in promise-ready setTimeout 
          // equivalent: delay()
          return Promise.delay(3000).then(function() {
            return poll();
          });
        } else {
          return 'final value!'
        }
      }));
  }

  return poll();
}

checkTransaction(1).then(function(result) {
  console.log('done:', result);
})

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.4.1/bluebird.min.js"></script>

0voto

Bamieh Points 4686

Vous pouvez renvoyer des promesses à partir de fonctions, et le .then de la fonction parentale sera résolue lorsque toutes les promesses retournées seront résolues.

Consultez cette page pour plus de détails.

https://gist.github.com/Bamieh/67c9ca982b20cc33c9766d20739504c8

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X