web-dev-qa-db-fra.com

Comment enchaîner un nombre variable de promesses dans Q, dans l'ordre?

J'ai vu enchaîner un nombre arbitraire de promesses dans Q ; ma question est différente. 

Comment effectuer un nombre d'appels variable, chacun renvoyant de manière asynchrone, dans l'ordre?
Le scénario est un ensemble de requêtes HTTP dont le nombre et le type sont déterminés par les résultats de la première requête HTTP.

J'aimerais faire ceci simplement. 

J'ai aussi vu cette réponse qui suggère quelque chose comme ceci: 

var q = require('q'),
    itemsToProcess =  ["one", "two", "three", "four", "five"];

function getDeferredResult(prevResult) {
  return (function (someResult) {
    var deferred = q.defer();
    // any async function (setTimeout for now will do, $.ajax() later)
    setTimeout(function () {
      var nextResult = (someResult || "Initial_Blank_Value ") + ".." + itemsToProcess[0];
      itemsToProcess = itemsToProcess.splice(1);
      console.log("tick", nextResult, "Array:", itemsToProcess);
      deferred.resolve(nextResult);
    }, 600);

    return deferred.promise;
  }(prevResult));
}

var chain = q.resolve("start");
for (var i = itemsToProcess.length; i > 0; i--) {
    chain = chain.then(getDeferredResult);
}

... mais il semble difficile de parcourir les itemsToProcess de cette manière. Ou pour définir une nouvelle fonction appelée "boucle" qui résume la récursion. Quel est le meilleur moyen?

32
Cheeso

Il existe un moyen simple et agréable de procéder avec [].reduce .

var chain = itemsToProcess.reduce(function (previous, item) {
    return previous.then(function (previousValue) {
        // do what you want with previous value
        // return your async operation
        return Q.delay(100);
    })
}, Q.resolve(/* set the first "previousValue" here */));

chain.then(function (lastResult) {
    // ...
});

reduce itère dans le tableau en transmettant la valeur renvoyée de l'itération précédente. Dans ce cas, vous retournez des promesses et, à chaque fois, vous enchaînez une then. Vous faites une promesse initiale (comme vous l'avez fait avec q.resolve("start")) de lancer les choses. 

Au début, cela peut prendre un certain temps pour comprendre ce qui se passe ici, mais si vous prenez un moment pour le parcourir, il est facile d’utiliser ce modèle, n’importe où, sans avoir à installer de machines.

75
Stuart K

Voici un concept de machine à états défini avec Q.

Supposons que vous ayez la fonction HTTP définie, donc elle renvoie un objet Q promise:

var Q_http = function (url, options) {
  return Q.when($.ajax(url, options));
}

Vous pouvez définir une fonction récursive nextState comme suit:

var states = [...]; // an array of states in the system.

// this is a state machine to control what url to get data from
// at the current state 
function nextState(current) {
  if (is_terminal_state(current))
    return Q(true);

  return Q_http(current.url, current.data).then(function (result) {
    var next = process(current, result);
    return nextState(next);
  });
}

function process(current, result) est une fonction permettant de déterminer l’étape suivante en fonction de l’état current et de la result de l’appel HTTP.

Lorsque vous l'utilisez, utilisez-le comme suit:

nextState(initial).then(function () {
  // all requests are successful.
}, function (reason) {
  // for some unexpected reason the request sequence fails in the middle.
});
1
yuxhuang

Je propose une autre solution, qui me paraisse plus facile à comprendre ... Vous faites comme si vous enchaîniez directement les promesses: promise.then(doSomethingFunction).then(doAnotherThingFunction);

Si nous mettons cela en boucle, nous obtenons ceci:

var chain = Q.when();
for(...) {
  chain = chain.then(functionToCall.bind(this, arg1, arg2));
};
chain.then(function() {
    console.log("whole chain resolved");
});


var functionToCall = function(arg1, arg2, resultFromPreviousPromise) {
}

Nous utilisons function currying pour utiliser plusieurs arguments. Dans notre exemple functionToCall.bind(this, arg1, arg2) retournera une fonction avec un argument: functionToCall(resultFromPreviousPromise)Vous n'avez pas besoin d'utiliser le résultat de la promesse précédente.

1
Marcel Böttcher

J'aime mieux cette façon:

var q = require('q'),
    itemsToProcess =  ["one", "two", "three", "four", "five"];

function getDeferredResult(a) {
  return (function (items) {
    var deferred;

    // end
    if (items.length === 0) {
      return q.resolve(true);
    }

    deferred = q.defer();

    // any async function (setTimeout for now will do, $.ajax() later)
    setTimeout(function () {
      var a = items[0];
      console.log(a);
      // pop one item off the array of workitems
      deferred.resolve(items.splice(1));
    }, 600);

    return deferred.promise.then(getDeferredResult);
  }(a));
}

q.resolve(itemsToProcess)
  .then(getDeferredResult);

La clé ici est d’appeler .then() sur le deferred.promise avec une version épissée du tableau d’éléments de travail. Cette then est exécutée après la résolution de la promesse initiale différée, qui se trouve dans le fn de setTimeout. Dans un scénario plus réaliste, la promesse reportée serait résolue dans le rappel du client HTTP. 

La q.resolve(itemsToProcess) initiale démarre les choses en transmettant les éléments de travail au premier appel du travail fn. 

J'ai ajouté cela dans l'espoir que cela aiderait les autres.

1
Cheeso