webentwicklung-frage-antwort-db.com.de

Wie kann man eine variable Anzahl von Versprechen in Q anordnen?

Ich habe gesehen, wie eine beliebige Anzahl von Versprechen in Q verkettet ist; Meine Frage ist anders. 

Wie kann ich eine variable Anzahl von Aufrufen durchführen, von denen jeder asynchron in der Reihenfolge zurückgegeben wird?
Das Szenario besteht aus einer Reihe von HTTP-Anforderungen, deren Anzahl und Typ durch die Ergebnisse der ersten HTTP-Anforderung bestimmt wird.

Ich möchte das einfach machen. 

Ich habe auch diese Antwort gesehen, die so etwas wie folgt suggeriert: 

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);
}

... aber es scheint umständlich, die itemsToProcess auf diese Weise zu durchlaufen. Oder um eine neue Funktion namens "Schleife" zu definieren, die die Rekursion abstrahiert. Was ist ein besserer Weg?

32
Cheeso

Es gibt eine schöne saubere Möglichkeit, dies mit [].reduce zu tun.

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 durchläuft das Array und gibt den zurückgegebenen Wert der vorherigen Iteration weiter. In diesem Fall kehrst du Versprechen zurück, und jedes Mal, wenn du eine then verkettest. Sie geben ein erstes Versprechen (wie bei q.resolve("start")), um die Dinge zu beginnen. 

Anfangs kann es eine Weile dauern, bis Sie den Kopf um das, was hier vor sich geht, umwickeln, aber wenn Sie sich einen Moment Zeit nehmen, um es durchzuarbeiten, ist es ein einfaches Muster, das Sie überall anwenden können, ohne dass Sie eine Maschine einrichten müssen.

75
Stuart K

Hier ist ein Konzept einer Zustandsmaschine, die mit Q definiert ist.

Angenommen, Sie haben die HTTP-Funktion definiert, sodass ein Q-Versprechungsobjekt zurückgegeben wird:

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

Sie können eine rekursive Funktion nextState wie folgt definieren:

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);
  });
}

Dabei ist function process(current, result) eine Funktion zum Ermitteln des nächsten Schritts gemäß dem current-Status und der result aus dem HTTP-Aufruf.

Wenn Sie es verwenden, verwenden Sie es wie folgt:

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

Ich schlage eine andere Lösung vor, die für mich verständlicher erscheint. Sie tun dasselbe, als würden Sie Versprechen direkt verketten: promise.then(doSomethingFunction).then(doAnotherThingFunction);

Wenn wir das in eine Schleife bringen, bekommen wir folgendes:

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) {
}

Wir verwenden function currying , um mehrere Argumente zu verwenden. In unserem Beispiel gibt functionToCall.bind(this, arg1, arg2) eine Funktion mit einem Argument zurück: functionToCall(resultFromPreviousPromise)Sie müssen das Ergebnis des vorherigen Versprechens nicht verwenden.

1

Ich mag diesen Weg besser:

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);

Der Schlüssel ist hier der Aufruf von .then() auf dem deferred.promise mit einer gespleißten Version des Arrays von Workitems. Diese then wird ausgeführt, nachdem das anfängliche verzögerte Versprechen aufgelöst wurde, das sich im fn für setTimeout befindet. In einem realistischeren Szenario würde das verzögerte Versprechen im http-Client-Rückruf aufgelöst. 

Die anfängliche q.resolve(itemsToProcess) startet durch die Übergabe der Arbeitselemente an den ersten Aufruf des Arbeitsfensters. 

Ich fügte hinzu, in der Hoffnung, es würde anderen helfen.

1
Cheeso