Wie kann ich eine Reihe von Versprechungen in sequentieller Reihenfolge ausführen?

81

Ich habe eine Reihe von Versprechungen, die in sequentieller Reihenfolge ausgeführt werden müssen.

var promises = [promise1, promise2, ..., promiseN];

Wenn Sie RSVP.all aufrufen, werden sie parallel ausgeführt:

RSVP.all(promises).then(...); 

Aber wie kann ich sie nacheinander ausführen?

Ich kann sie so manuell stapeln

RSVP.resolve()
    .then(promise1)
    .then(promise2)
    ...
    .then(promiseN)
    .then(...);

Das Problem ist jedoch, dass die Anzahl der Versprechen variiert und die Anzahl der Versprechen dynamisch aufgebaut wird.

jaaksarv
quelle
Aus den anderen Antworten und Abstimmungen zu meinen geht hervor, dass mehr Leute die rsvp-README lesen müssen, in der erklärt wird: "Der wirklich großartige Teil kommt, wenn Sie ein Versprechen vom ersten Handler zurückgeben." Wenn Sie dies nicht tun, verpassen Sie wirklich die Ausdruckskraft von Versprechungen.
Michael Johnston
Ähnliche Frage, aber nicht Framework-spezifisch: stackoverflow.com/q/24586110/245966
jakub.g

Antworten:

136

Wenn Sie sie bereits in einem Array haben, werden sie bereits ausgeführt. Wenn Sie ein Versprechen haben, wird es bereits ausgeführt. Dies ist kein Problem von Versprechungen (dh sie sind Taskin dieser Hinsicht nicht wie C # s mit der .Start()Methode). .allführt nichts aus, sondern gibt nur ein Versprechen zurück.

Wenn Sie eine Reihe von Funktionen zur Rückgabe von Versprechen haben:

var tasks = [fn1, fn2, fn3...];

tasks.reduce(function(cur, next) {
    return cur.then(next);
}, RSVP.resolve()).then(function() {
    //all executed
});

Oder Werte:

var idsToDelete = [1,2,3];

idsToDelete.reduce(function(cur, next) {
    return cur.then(function() {
        return http.post("/delete.php?id=" + next);
    });
}, RSVP.resolve()).then(function() {
    //all executed
});
Esailija
quelle
3
Dies ist eine hervorragende Möglichkeit, einen Baum homogener Versprechen zu erstellen, für die keine Argumente erforderlich sind. Dies entspricht genau der Verwendung eines next_promise-Zeigers, um den Baum selbst zu erstellen. Dies ist erforderlich, wenn die Versprechungen in Bezug auf Argumente usw. nicht homogen sind. Die Reduktionsfunktion führt lediglich den Zeiger auf den aktuellen Wert aus -Blattbiss für dich. Sie möchten auch den Baum von sich selbst bauen, wenn einige Ihrer Dinge gleichzeitig passieren können. In einem Baum der Verheißungen sind Zweige Sequenzen und Blätter gleichzeitig.
Michael Johnston
Vielen Dank für Ihre Antwort. Sie haben Recht, dass das Erstellen eines Versprechens bereits bedeutet, dass es ausgeführt wird, sodass meine Frage nicht richtig formuliert wurde. Am Ende habe ich mein Problem ohne Versprechen anders gelöst.
Jaaksarv
1
@SSHThis Nun zuallererst, wat. Zweitens wird die vorherige Antwort an übergeben .then, in diesem Beispiel wird sie einfach ignoriert ...
Esailija
3
Wenn eines dieser Versprechen fehlschlägt, wird der Fehler niemals zurückgewiesen und das Versprechen wird niemals gelöst ...
Maxwelll
5
Wenn Sie sie bereits in einem Array haben, werden sie bereits ausgeführt. - Dieser Satz sollte fett und größer sein. Es ist wichtig zu verstehen.
Ducin
22

Mit asynchronen Funktionen von ECMAScript 2017 würde dies folgendermaßen geschehen:

async function executeSequentially() {
    const tasks = [fn1, fn2, fn3]

    for (const fn of tasks) {
        await fn()
    }
}

Mit BabelJS können Sie jetzt asynchrone Funktionen verwenden

ujeenator
quelle
Dies sollte jetzt (2020) der Standardansatz sein. Für Erstbenutzer kann es wichtig sein, hier zwei Dinge zu beachten: 1. Sobald ein Versprechen besteht, geht es bereits. Es ist also wirklich wichtig, dass 2. fn1, fn2, fn3hier Funktionen sind, z. B. () => yourFunctionReturningAPromise()im Gegensatz zu nur yourFunctionReturningAPromise(). Dies ist auch der Grund, warum await fn()stattdessen nur notwendig ist await fn. Weitere Informationen finden Sie in den offiziellen Dokumenten . Entschuldigung für das Posten als Kommentar, aber die Bearbeitungswarteschlange ist voll :)
ezmegy
7

ES7 Weg im Jahr 2017.

  <script>
  var funcs = [
    _ => new Promise(resolve => setTimeout(_ => resolve("1"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("2"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("3"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("4"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("5"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("6"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("7"), 1000))
  ];
  async function runPromisesInSequence(promises) {
    for (let promise of promises) {
      console.log(await promise());
    }
  }
  </script>
  <button onClick="runPromisesInSequence(funcs)">Do the thing</button>

Dadurch werden die angegebenen Funktionen nacheinander (einzeln) und nicht parallel ausgeführt. Der Parameter promisesist ein Array von Funktionen, die zurückgeben Promise.

Plunker-Beispiel mit dem obigen Code: http://plnkr.co/edit/UP0rhD?p=preview

allenhwkim
quelle
4

Ein zweiter Versuch einer Antwort, in der ich versuche, erklärender zu sein:

Zunächst einige erforderliche Hintergrundinformationen aus der RSVP-README :

Der wirklich großartige Teil kommt, wenn Sie ein Versprechen vom ersten Handler zurückgeben ... Dies ermöglicht es Ihnen, verschachtelte Rückrufe zu reduzieren, und ist das Hauptmerkmal von Versprechungen, die ein "Rechtsdriften" in Programmen mit viel asynchronem Code verhindern.

Genau so machen Sie Versprechen nacheinander, indem Sie das spätere Versprechen von thendem Versprechen zurückgeben, das davor enden sollte.

Es ist hilfreich, sich eine solche Reihe von Versprechungen als einen Baum vorzustellen, in dem die Zweige sequentielle Prozesse und die Blätter gleichzeitige Prozesse darstellen.

Der Prozess des Aufbaus eines solchen Baums von Versprechungen ist analog zu der sehr häufigen Aufgabe, andere Arten von Bäumen zu bauen: Behalten Sie einen Zeiger oder Verweis darauf bei, wo in dem Baum Sie gerade Zweige hinzufügen, und fügen Sie iterativ Dinge hinzu.

Wie @Esailija in seiner Antwort betonte, können Sie reduceden Baum ordentlich erstellen , wenn Sie über eine Reihe von Funktionen verfügen, die Versprechen zurückgeben und keine Argumente enthalten . Wenn Sie Reduce jemals für sich selbst implementiert haben, werden Sie verstehen, dass Reduce hinter den Kulissen in @ Esailijas Antwort darin besteht, einen Verweis auf das aktuelle Versprechen ( cur) beizubehalten und jedes Versprechen das nächste Versprechen in seinem zurückgeben zu lassen then.

Wenn Sie KEINE schöne Reihe homogener (in Bezug auf die Argumente, die sie annehmen / zurückgeben) Versprechen zurückgebender Funktionen haben oder wenn Sie eine kompliziertere Struktur als eine einfache lineare Sequenz benötigen, können Sie den Baum der Versprechen selbst erstellen, indem Sie ihn beibehalten Ein Verweis auf die Position im Versprechungsbaum, an der Sie neue Versprechungen hinzufügen möchten:

var root_promise = current_promise = Ember.Deferred.create(); 
// you can also just use your first real promise as the root; the advantage of  
// using an empty one is in the case where the process of BUILDING your tree of 
// promises is also asynchronous and you need to make sure it is built first 
// before starting it

current_promise = current_promise.then(function(){
  return // ...something that returns a promise...;
});

current_promise = current_promise.then(function(){
  return // ...something that returns a promise...;
});

// etc.

root_promise.resolve();

Sie können Kombinationen von gleichzeitigen und sequentiellen Prozessen erstellen, indem Sie RSVP.all verwenden, um einem versprochenen "Zweig" mehrere "Blätter" hinzuzufügen. Meine Antwort, die als zu kompliziert eingestuft wurde, zeigt ein Beispiel dafür.

Sie können auch Ember.run.scheduleOnce ('afterRender') verwenden, um sicherzustellen, dass etwas, das in einem Versprechen getan wurde, gerendert wird, bevor das nächste Versprechen ausgelöst wird. Meine Antwort, die als zu kompliziert eingestuft wurde, zeigt auch ein Beispiel dafür.

Michael Johnston
quelle
3
Das ist viel besser, aber ich habe das Gefühl, dass Sie immer noch vom Thema abweichen. Dies ist vielen Antworten auf Versprechen gemeinsam. Die Leute scheinen sich nicht die Zeit zu nehmen, die Frage zu lesen, sondern kommentieren einfach einen Aspekt von Versprechen, den sie persönlich verstehen. Die ursprüngliche Frage beinhaltet keine parallele Ausführung, nicht einmal ein bisschen, und sie zeigt deutlich, dass eine einfache Verkettung über thengewünscht ist. Sie haben viele zusätzliche Informationen angegeben, die die Antwort auf die gestellte Frage verbergen.
David McMullin
@DavidMcMullin ".... und es zeigt deutlich, dass eine einfache Verkettung über dann erwünscht ist ...", aber tatsächlich gibt er an, dass die Reihenfolge der Versprechen dynamisch aufgebaut wird. Er muss also verstehen, wie man einen Baum konstruiert, auch wenn es in diesem Fall die einfache Teilmenge der "linearen Sequenz" des Baums ist. Sie müssen es noch erstellen, indem Sie einen Verweis auf das letzte Versprechen in der Kette beibehalten und neue Versprechen hinzufügen.
Michael Johnston
Als OP sagte, die "Anzahl der Versprechen variiert und das Array der Versprechen wird dynamisch aufgebaut", war ich mir ziemlich sicher, dass alles, was er meinte, war, dass die Größe des Arrays nicht vorbestimmt war und dass er / sie daher kein einfaches verwenden konnte Promise.resolve().then(...).then(...)..., nicht dass das Array wuchs, während die Versprechen ausgeführt wurden. Natürlich ist jetzt alles umstritten.
JLRishe
4

Ein weiterer Ansatz besteht darin, eine globale Sequenzfunktion für den PromisePrototyp zu definieren .

Promise.prototype.sequence = async (promiseFns) => {
  for (let promiseFn of promiseFns) {
    await promiseFn();
  }
}

Dann können Sie es überall verwenden, genau wie Promise.all()

Beispiel

const timeout = async ms => new Promise(resolve =>
  setTimeout(() => {
    console.log("done", ms);
    resolve();
  }, ms)
);

// Executed one after the other
await Promise.sequence([() => timeout(1000), () => timeout(500)]);
// done: 1000
// done: 500

// Executed in parallel
await Promise.all([timeout(1000), timeout(500)]);
// done: 500
// done: 1000

Haftungsausschluss: Bearbeiten Sie Prototypen sorgfältig!

Ben Winding
quelle
2

Alles was benötigt wird um das zu lösen ist eine forSchleife :)

var promises = [a,b,c];
var chain;

for(let i in promises){
  if(chain) chain = chain.then(promises[i]);
  if(!chain) chain = promises[i]();
}

function a(){
  return new Promise((resolve)=>{
    setTimeout(function(){
      console.log('resolve A');
      resolve();
    },1000);
  });
}
function b(){
  return new Promise((resolve)=>{
    setTimeout(function(){
      console.log('resolve B');
      resolve();
    },500);
  });
}
function c(){
  return new Promise((resolve)=>{
    setTimeout(function(){
      console.log('resolve C');
      resolve();
    },100);
  });
}
Paweł
quelle
Warum hat if(!chain) chain = promises[i]();ein ()am Ende? Ich denke, in dem Fall, in dem die Kette leer ist (Iteration 0), möchte man nur das rohe Versprechen haben, und dann kann die Schleife jedes nachfolgende Versprechen in das der Kette einfügen .then(). Wäre das nicht if(!chain) chain = promises[i];? Vielleicht habe ich hier etwas nicht verstanden.
Halfer
Ah - Sie a,b,csind in der Tat Funktionen, die Versprechen zurückgeben, und keine Versprechen. Das Obige macht also Sinn. Aber welchen Nutzen hat es, die Versprechen auf diese Weise zu verpacken?
Halfer
2

Ich hatte ein ähnliches Problem und habe eine rekursive Funktion erstellt, die Funktionen nacheinander nacheinander ausführt.

var tasks = [fn1, fn2, fn3];

var executeSequentially = function(tasks) {
  if (tasks && tasks.length > 0) {
    var task = tasks.shift();

    return task().then(function() {
      return executeSequentially(tasks);
    });
  }

  return Promise.resolve();  
};

Falls Sie die Ausgabe dieser Funktionen erfassen müssen:

var tasks = [fn1, fn2, fn3];

var executeSequentially = function(tasks) {
  if (tasks && tasks.length > 0) {
    var task = tasks.shift();

    return task().then(function(output) {
      return executeSequentially(tasks).then(function(outputs) {
        outputs.push(output);

        return Promise.resolve(outputs);  
      });
    });
  }

  return Promise.resolve([]);
};
mrded
quelle
0
export type PromiseFn = () => Promise<any>;

export class PromiseSequence {
  private fns: PromiseFn[] = [];

  push(fn: PromiseFn) {
    this.fns.push(fn)
  }

  async run() {
    for (const fn of this.fns) {
      await fn();
    }
  }
}

dann

const seq = new PromiseSequence();
seq.push(() => Promise.resolve(1));
seq.push(() => Promise.resolve(2));
seq.run();

Es ist auch möglich, die zurückgegebenen Versprechen in einer anderen privaten Variable zu speichern und an Rückrufe weiterzugeben

Daniel Khoroshko
quelle
-1

Das, wonach ich gesucht habe, war im Wesentlichen mapSeries, und ich bin dabei, das Speichern über eine Reihe von Werten abzubilden, und ich möchte die Ergebnisse.

Also, so weit ich gekommen bin, um anderen zu helfen, in Zukunft nach ähnlichen Dingen zu suchen.

(Beachten Sie, dass der Kontext eine Ember-App ist).

App = Ember.Application.create();

App.Router.map(function () {
    // put your routes here
});

App.IndexRoute = Ember.Route.extend({
    model: function () {
            var block1 = Em.Object.create({save: function() {
                return Em.RSVP.resolve("hello");
            }});
    var block2 = Em.Object.create({save: function() {
            return Em.RSVP.resolve("this");
        }});
    var block3 = Em.Object.create({save: function() {
        return Em.RSVP.resolve("is in sequence");
    }});

    var values = [block1, block2, block3];

    // want to sequentially iterate over each, use reduce, build an array of results similarly to map...

    var x = values.reduce(function(memo, current) {
        var last;
        if(memo.length < 1) {
            last = current.save();
        } else {
            last = memo[memo.length - 1];
        }
        return memo.concat(last.then(function(results) {
            return current.save();
        }));
    }, []);

    return Ember.RSVP.all(x);
    }
});
Julian Leviston
quelle