Sind verschachtelte Versprechen in Node.js normal?

Lesezeit: 14 Minuten

Benutzer-Avatar
Grimmig

Das Problem, mit dem ich jetzt seit zwei Wochen zu kämpfen habe, während ich Node.js lerne, ist die synchrone Programmierung mit node. Ich habe das festgestellt, egal wie ich versuche, Dinge zu tun der Reihe nach Ich lande immer bei verschachtelten Versprechungen. Ich habe festgestellt, dass es Module wie Q gibt, die bei der Verkettung von Versprechungen bis hin zur Wartbarkeit helfen.

Was ich bei der Recherche nicht verstehe ist Promise.all(), Promise.resolve() und Promise.reject(). Promise.reject ist durch den Namen ziemlich selbsterklärend, aber wenn ich eine Anwendung schreibe, bin ich verwirrt darüber, wie man diese in Funktionen oder Objekte einbezieht, ohne das Verhalten der Anwendung zu beeinträchtigen.

Es gibt definitiv eine Lernkurve für Node.js, wenn man von einer Programmiersprache wie Java oder C# kommt. Bleibt noch die Frage, ob Promise Chaining in Node.js normal ist (Best Practice).

Beispiel:

driver.get('https://website.example/login').then(function () {
    loginPage.login('company.admin', 'password').then(function () {
        var employeePage = new EmployeePage(driver.getDriver());

        employeePage.clickAddEmployee().then(function() {
            setTimeout(function() {
                var addEmployeeForm = new AddEmployeeForm(driver.getDriver());

                addEmployeeForm.insertUserName(employee.username).then(function() {
                    addEmployeeForm.insertFirstName(employee.firstName).then(function() {
                        addEmployeeForm.insertLastName(employee.lastName).then(function() {
                            addEmployeeForm.clickCreateEmployee().then(function() {
                                employeePage.searchEmployee(employee);
                            });
                        });
                    });
                });
            }, 750);
        });
    });
});

  • Hier ist eine sehr ähnliche Frage, die ich vor einiger Zeit gestellt habe

    – Benjamin Grünbaum

    5. März 2016 um 9:43 Uhr


Benutzer-Avatar
Tate Thurston

Nein, einer der großen Vorteile von Promises ist, dass Sie Ihren asynchronen Code linear statt verschachtelt halten können (Callback-Hölle vom Continuation-Passing-Stil).

Promises geben Ihnen Return-Statements und Error Throwing, die Sie bei Continuation Passing Style verlieren.

Sie müssen das Versprechen von Ihren asynchronen Funktionen zurückgeben, damit Sie den zurückgegebenen Wert verketten können.

Hier ist ein Beispiel:

driver.get('https://website.example/login')
  .then(function() {
    return loginPage.login('company.admin', 'password')
  })
  .then(function() {
    var employeePage = new EmployeePage(driver.getDriver());
    return employeePage.clickAddEmployee();
  })
  .then(function() {
    setTimeout(function() {
      var addEmployeeForm = new AddEmployeeForm(driver.getDriver());

      addEmployeeForm.insertUserName(employee.username)
        .then(function() {
          return addEmployeeForm.insertFirstName(employee.firstName)
        })
        .then(function() {
          return addEmployeeForm.insertLastName(employee.lastName)
        })
        .then(function() {
          return addEmployeeForm.clickCreateEmployee()
        })
        .then(function() {
          return employeePage.searchEmployee(employee)
        });
    }, 750);
});

Promise.all nimmt ein Array von Promises und wird aufgelöst, sobald alle Promises aufgelöst sind, wenn irgendwelche abgelehnt werden, wird das Array abgelehnt. Auf diese Weise können Sie asynchronen Code gleichzeitig statt seriell ausführen und trotzdem auf das Ergebnis aller gleichzeitigen Funktionen warten. Wenn Sie mit einem Thread-Modell vertraut sind, denken Sie an das Spawnen von Threads und dann an das Beitreten.

Beispiel:

addEmployeeForm.insertUserName(employee.username)
    .then(function() {
        // these two functions will be invoked immediately and resolve concurrently
        return Promise.all([
            addEmployeeForm.insertFirstName(employee.firstName),
            addEmployeeForm.insertLastName(employee.lastName)
        ])
    })
    // this will be invoked after both insertFirstName and insertLastName have succeeded
    .then(function() {
        return addEmployeeForm.clickCreateEmployee()
    })
    .then(function() {
        return employeePage.searchEmployee(employee)
    })
    // if an error arises anywhere in the chain this function will be invoked
    .catch(function(err){
        console.log(err)
    });

Promise.resolve() und Promise.reject() sind Methoden, die beim Erstellen von a verwendet werden Promise. Sie werden verwendet, um eine asynchrone Funktion mithilfe von Rückrufen zu umschließen, sodass Sie mit Promises anstelle von Rückrufen arbeiten können.

Beschließen wird das Versprechen lösen/erfüllen (das bedeutet eine verkettete then -Methode wird mit dem resultierenden Wert aufgerufen).
Ablehnen wird das Versprechen ablehnen (d.h. jede angekettete then Methode(n) werden nicht aufgerufen, sondern die erste verkettet catch -Methode wird mit dem aufgetretenen Fehler aufgerufen).

Ich habe deine verlassen setTimeout vorhanden, um das Verhalten Ihres Programms beizubehalten, aber es ist wahrscheinlich unnötig.

  • Danke, das hat mir sehr geholfen.

    – Grimm

    7. März 2016 um 13:22 Uhr

  • @TateThurston Vergib mir, dass ich zu noob bin. Ich habe eine Frage, werden verkettete Versprechen (wie in Ihrem ersten Beispiel) nacheinander ausgeführt? Nehmen wir also an, das erste Versprechen dauerte etwa 1 Sekunde, um die asynchrone Anforderung abzuschließen, das zweite Versprechen verkettete sich .then wird nicht ausgeführt, bis der erste fertig ist, ist das richtig? Und ist es das gleiche für das 3. Versprechen und so weiter?

    – JohnnyQ

    21. Januar 2017 um 14:37 Uhr

  • Verstehe ich richtig, dass Sie, wenn Sie mehrere asynchrone Aufrufe haben, die auf dem Ergebnis eines anderen asynchronen Aufrufs beruhen, immer noch verschachtelte Versprechungen haben? Es gibt wirklich keine Möglichkeit, es zu vermeiden.

    – Levi Fuller

    9. März 2017 um 17:51 Uhr

  • @LeviFuller Sie können die Dinge immer noch flach halten. Wenn Sie einen konkreten Fall im Auge haben, beantworte ich ihn gerne.

    – Tate Thurston

    10. März 2017 um 20:39 Uhr


  • @TateThurston Das wäre großartig. Wie könnte ich zum Beispiel so etwas abflachen, wo jeder Aufruf auf dem Ergebnis des vorherigen Aufrufs beruht? getOrganization().then(orgId => getFirstEnvironment(orgId).then(environmentId => getAppList.then(appList => console.log(appList)))).catch(err => console.log(err));

    – Levi Fuller

    10. März 2017 um 22:07 Uhr

Verwenden async Bibliothek und Nutzung async.series anstelle von verschachtelten Verkettungen, die wirklich hässlich und schwer zu debuggen/verstehen aussehen.

async.series([
    methodOne,
    methodTwo
], function (err, results) {
    // Here, results is the value from each function
    console.log(results);
});

Das Promise.all(iterable) -Methode gibt ein Promise zurück, das aufgelöst wird, wenn alle Promises im iterierbaren Argument aufgelöst wurden, oder lehnt mit dem Grund des ersten übergebenen Promise ab, das abgelehnt wird.

var p1 = Promise.resolve(3);
var p2 = 1337;
var p3 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 100, "foo");
}); 

Promise.all([p1, p2, p3]).then(function(values) { 
  console.log(values); // [3, 1337, "foo"] 
});

Das Promise.resolve(value) -Methode gibt ein Promise-Objekt zurück, das mit dem angegebenen Wert aufgelöst wird. Wenn der Wert ein Thenable ist (dh eine Then-Methode hat), “folgt” das zurückgegebene Promise diesem Thenable und nimmt seinen endgültigen Zustand an; Andernfalls wird das zurückgegebene Versprechen mit dem Wert erfüllt.

var p = Promise.resolve([1,2,3]);
p.then(function(v) {
  console.log(v[0]); // 1
});

https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

  • Sie haben bei weitem das sauberste Beispiel mit async.series und Promise.all. Verhalten sich die Ergebnisse für async.series genauso wie die Werte von Promise.all, wo es sich um ein Array von Werten handelt?

    – Grimm

    4. März 2016 um 21:04 Uhr

  • Nehmen wir auch an, ich verwende async.series() in einer Funktion innerhalb eines Objekts und ich muss Versprechungen innerhalb dieser Funktion verketten. Würde ich async.series() zurückgeben oder nichts an den Aufrufer zurückgeben?

    – Grimm

    4. März 2016 um 21:06 Uhr

  • Es gibt hier keinen Grund, die zu verwenden async Bibliothek, um Verschachtelungen zu vermeiden. Das OP kann seine Promise-Aufrufe ohne Verschachtelung verketten.

    – jfriend00

    4. März 2016 um 21:54 Uhr

  • @jfriend00 Wann würden Sie die asynchrone Bibliothek verwenden?

    – Grimm

    4. März 2016 um 22:56 Uhr

  • @CharlesSexton – Sobald Sie gelernt haben, wie man Versprechungen verwendet, und sich an die verbesserte Fehlerbehandlung für asynchrone Vorgänge und die allgemeine einfache Verwaltung von asynchronen Vorgängen gewöhnt haben, werden Sie niemals asynchronen Code ohne ihn schreiben wollen, ich habe nichts gefunden, was asynchron ist Bibliotheksangebote, die mit so etwas wie der Bluebird Promise Library (die ich benutze) nicht so einfach oder einfacher gemacht werden können. Ich verwende die asynchrone Bibliothek nicht, weil ich keinen Grund dafür gefunden habe, sobald ich Versprechungen gelernt habe.

    – jfriend00

    4. März 2016 um 23:01 Uhr

Benutzer-Avatar
Arun Sivasankaran

Ich habe die unnötige Verschachtelung entfernt. Ich verwende die Syntax von ‘bluebird’ (meine bevorzugte Promise-Bibliothek)
http://bluebirdjs.com/docs/api-reference.html

var employeePage;

driver.get('https://website.example/login').then(function() {
    return loginPage.login('company.admin', 'password');
}).then(function() {
    employeePage = new EmployeePage(driver.getDriver());
    return employeePage.clickAddEmployee();
}).then(function () {
    var deferred = Promise.pending();
    setTimeout(deferred.resolve,750);
    return deferred.promise;
}).then(function() {
    var addEmployeeForm = new AddEmployeeForm(driver.getDriver());
    return Promise.all([addEmployeeForm.insertUserName(employee.username),
                        addEmployeeForm.insertFirstName(employee.firstName),
                        addEmployeeForm.insertLastName(employee.lastName)]);
}).then(function() {
    return addEmployeeForm.clickCreateEmployee();
}).then(function() {
    return employeePage.searchEmployee(employee);
}).catch(console.log);

Ich habe Ihren Code so geändert, dass er Beispiele für alle Ihre Fragen enthält.

  1. Beim Arbeiten mit Zusagen muss die asynchrone Bibliothek nicht verwendet werden. Promises sind an sich sehr mächtig und ich denke, es ist ein Anti-Pattern, Promises und Libraries wie Async zu mischen.

  2. Normalerweise sollten Sie es vermeiden, den Stil var deferred = Promise.pending() zu verwenden … es sei denn

‘beim Wrappen einer Callback-API, die nicht der Standardkonvention folgt. Wie setTimeout:’

https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns

Für das setTimeout-Beispiel … erstellen Sie ein “verzögertes” Promise … lösen Sie das Promise innerhalb von setTimeout auf und geben Sie das Promise dann außerhalb von setTimeout zurück. Dies mag ein wenig unintuitiv erscheinen. Schauen Sie sich dieses Beispiel an, ich habe eine andere Frage beantwortet. Q.js-Versprechen mit node. Fehlender Fehlerhandler auf `socket`. TypeError: Methode ‘then’ von undefined kann nicht aufgerufen werden

Normalerweise können Sie Promise.promisify(someFunction) verwenden, um eine Callback-Funktion in eine Promise-Rückgabefunktion umzuwandeln.

  1. Promise.all Angenommen, Sie führen mehrere Aufrufe an einen Dienst durch, der asynchron zurückkehrt. Wenn sie nicht voneinander abhängig sind, können Sie die Anrufe gleichzeitig tätigen.

Übergeben Sie die Funktionsaufrufe einfach als Array. Versprechen.all([promiseReturningCall1, promiseReturningCall2, promiseReturningCall3]);

  1. Fügen Sie schließlich ganz am Ende einen catch-Block hinzu, um sicherzustellen, dass Sie jeden Fehler abfangen. Dadurch wird jede Ausnahme irgendwo in der Kette abgefangen.

  • Wo bekommt man Promise.pending() aus? Das ist keine mir bekannte Standardmethode von Promise. Warum nicht einfach den Standard-Promise-Konstruktor verwenden? Außerdem riecht eine willkürliche Verzögerung, die in diesen Prozess eingefügt wurde (von der ich weiß, dass sie nicht Ihre Idee war, sondern vom OP stammt), nach einem Hack, der wahrscheinlich nicht der richtige Weg ist, dies zu codieren.

    – jfriend00

    4. März 2016 um 23:05 Uhr


  • Promise.pending ist eine Bluebird-Syntax. Ich stimme zu, dass die Verwendung von setTimeout komisch aussieht. Ich habe es drin gelassen, um die manuelle Promise-Erstellung zu erklären.

    – Arun Sivasankaran

    4. März 2016 um 23:26 Uhr


  • Dann zwei Punkte. 1) Sie sollten in Ihrer Antwort sagen, dass Sie sich auf Bluebird verlassen (das ist auch meine bevorzugte Versprechungsbibliothek), da es über Standardversprechen hinausgeht. 2) Wenn Sie Bluebird verwenden, können Sie es auch verwenden Promise.delay() Anstatt von Promise.pending() und setTimeout().

    – jfriend00

    4. März 2016 um 23:28 Uhr

  • Ich habe meine Antwort aktualisiert, um “Bluebird” zu erwähnen. Promise.delay ist tatsächlich der bessere Weg, um manuelle Verzögerungen hinzuzufügen.

    – Arun Sivasankaran

    4. März 2016 um 23:35 Uhr


  • Interessant, dass ich das nicht sehe Promise.pending() auf der Bluebird API-Dokumentationsseite. Ist das eine ältere oder neuere API?

    – jfriend00

    5. März 2016 um 0:13 Uhr


Benutzer-Avatar
Mulan

Ich habe gerade eine ähnliche Frage beantwortet, in der ich eine Technik erklärt habe, die Generatoren verwendet, um Promise-Ketten auf nette Weise zu glätten. Die Technik wird von Co-Routinen inspiriert.

Nehmen Sie dieses Stück Code

Promise.prototype.bind = Promise.prototype.then;

const coro = g => {
  const next = x => {
    let {done, value} = g.next(x);
    return done ? value : value.bind(next);
  }
  return next();
};

Mit ihm können Sie Ihre tief verschachtelte Promise-Kette in diese umwandeln

coro(function* () {
  yield driver.get('https://website.example/login')
  yield loginPage.login('company.admin', 'password');
  var employeePage = new EmployeePage(driver.getDriver());
  yield employeePage.clickAddEmployee();
  setTimeout(() => {
    coro(function* () {
      var addEmployeeForm = new AddEmployeeForm(driver.getDriver());
      yield addEmployeeForm.insertUserName(employee.username);
      yield addEmployeeForm.insertFirstName(employee.firstName);
      yield addEmployeeForm.insertLastName(employee.lastName);
      yield addEmployeeForm.clickCreateEmployee();
      yield employeePage.searchEmployee(employee);
    }());
  }, 750);
}());

Mit benannten Generatoren können wir das noch lesbarer machen

// don't forget to assign your free variables
// var driver = ...
// var loginPage = ...
// var employeePage = new EmployeePage(driver.getDriver());
// var addEmployeeForm = new AddEmployeeForm(driver.getDriver());
// var employee = ...

function* createEmployee () {
  yield addEmployeeForm.insertUserName(employee.username);
  yield addEmployeeForm.insertFirstName(employee.firstName);
  yield addEmployeeForm.insertLastName(employee.lastName);
  yield addEmployeeForm.clickCreateEmployee();
  yield employeePage.searchEmployee(employee);
}

function* login () {
  yield driver.get('https://website.example/login')
  yield loginPage.login('company.admin', 'password');
  yield employeePage.clickAddEmployee();
  setTimeout(() => coro(createEmployee()), 750);
}

coro(login());

Dies kratzt jedoch nur an der Oberfläche dessen, was mit Co-Routinen möglich ist, um den Fluss von Versprechungen zu kontrollieren. Lesen Sie die oben verlinkte Antwort, die einige der anderen Vorteile und Fähigkeiten dieser Technik demonstriert.

Wenn Sie beabsichtigen, Co-Routinen für diesen Zweck zu verwenden, empfehle ich Ihnen, sich die anzusehen co Bibliothek.

PS nicht sicher, warum Sie verwenden setTimeout in dieser Mode. Was bringt es, speziell auf 750 ms zu warten?

Benutzer-Avatar
GoldbemaltZitronen

Da dieser Beitrag ein Top-Ergebnis für „verschachtelte Versprechungen“ bei Google ist und ich in meinen frühen Tagen, als ich node.js aus einem C#-Hintergrund lernte, mit Versprechungen zu kämpfen hatte, dachte ich, ich würde etwas posten, das anderen helfen würde, einen ähnlichen Übergang zu machen/ Evolution.

Die positive Antwort von Tate ist insofern völlig richtig, als sie eine Sequenz erzwingt, aber das Problem für die meisten .NET- oder Java-Entwickler ist, dass wir einfach nicht daran gewöhnt sind, dass so viele Dinge asynchrone Operationen in einer synchronen Sprache sind. Sie müssen sich sehr bewusst sein, was asynchron ist, da äußere Blöcke fortgesetzt und abgeschlossen werden, bevor eine asynchrone Aktion dies tun würde.

Zur Veranschaulichung hier etwas Code (komplett mit Verschachtelung und zwei Fehlern!), mit dem ich Probleme hatte, als ich mit „pg-promise“ Promises lernte:

            exports.create = async function createMeet(thingJson, res, next) {
    let conn;
    if (helpers.isDate(helpers.isStringDate(thingJson.ThingDate))){
        db.connect()
            .then(obj => {
                conn = obj;
                conn.proc('spCreateThing',[
                    thingJson.ThingName,
                    thingJson.ThingDescription,
                    thingJson.ThingDate])
                    .then(data => {
                        res.status(201).json(data);
                        res.send();
                    })
                    .catch(error =>{
                        console.error("Error creating a Thing via spCreateThing(" + thingJson + "): " + error);
                        next(createError(500, "Failed to create a Thing!"));
                    })
                    .finally(()  => {
                        conn.done(); //appropriate time to close the connection
                    });
                })
            .catch(error =>{
                console.error("Error establishing postgres database connection: " + error);
                next(createError(500, "Error establishing postgres database connection: " + error));
            })
            .finally(()  => { //this finally block will execute before the async actions fired in first .then() complete/start
                    conn.done(); //so this would close the connection before conn.proc() has completed/started
            });
        res.send(); //this will execute immediately following .connect() BEFORE any of the chained promise results,
        // thus sending a response before we've even figured out if the connection was successful and started the proc 
    } else {
        console.error("Attempt to create a Thing without valid date: " + thingJson.ThingDate);
        next(createError(400, "Must specify a valid date: " + thingJson.ThingDate));
    }

Darüber hinaus wird der Code, der diese Funktion aufruft (dh ein Routenhandler), abgeschlossen, bevor der DB-Verbindungsprozess überhaupt beginnt.

Das Netz davon sind also die äußeren Funktionen definieren die Versprechenstruktur und einleiten die async-anrufe, aber dann sofort vervollständigen ihren Block, da JS zunächst eine synchrone Sprache ist; Seien Sie sich also bewusst und gehen Sie davon aus, dass dies nicht einmal alle asynchronen Aufrufe tun Anfang bis um nach Der Block, der es aufgerufen hat, ist vollständig.

Ich weiß, dass dies für Berufs-JS-Entwickler offensichtlich ist (und ist es jetzt auch für mich), aber ich hoffe, dass dies anderen wirklich hilft, die diese Konzepte noch nicht kennen.

  • Zum einen sollten Sie verwenden async/await Syntax, die viel einfacher ist. Und zum anderen macht man es nicht nötig connect Anruf mit pg-Promise. Ihr Datenbankcode kann auf nur eine Zeile reduziert werden – await db.proc(...) und das ist es.

    – vitaly-t

    22. Mai 2021 um 14:02 Uhr


Benutzer-Avatar
Gemeinschaft

Ihr nächster Schritt besteht darin, von der Verschachtelung zur Verkettung überzugehen. Sie müssen erkennen, dass jedes Versprechen ein isoliertes Versprechen ist, das in einem übergeordneten Versprechen verkettet werden kann. Mit anderen Worten, Sie können die Versprechen zu einer Kette glätten. Jedes Promise-Ergebnis kann an das nächste weitergegeben werden.

Hier ist ein toller Blogbeitrag dazu: Abflachung von Versprechensketten. Es verwendet Angular, aber Sie können das ignorieren und sich ansehen, wie sich eine tiefe Verschachtelung von Versprechen in eine Kette verwandelt.

Eine weitere gute Antwort finden Sie hier auf StackOverflow: Javascript Promises verstehen; Stapeln und Verketten.

  • Zum einen sollten Sie verwenden async/await Syntax, die viel einfacher ist. Und zum anderen macht man es nicht nötig connect Anruf mit pg-Promise. Ihr Datenbankcode kann auf nur eine Zeile reduziert werden – await db.proc(...) und das ist es.

    – vitaly-t

    22. Mai 2021 um 14:02 Uhr


Benutzer-Avatar
Stephan Ostermüller

Ja, wie @TateThurston sagte, wir verketten sie. Es ist noch ästhetischer, wenn Sie es6-Pfeilfunktionen verwenden 😋

Hier ist ein Beispiel:

driver
    .get( 'https://website.example/login' )
    .then( () => loginPage.login( 'company.admin', 'password' ) )
    .then( () => new EmployeePage( driver.getDriver() ).clickAddEmployee() )
    .then( () => {
        setTimeout( () => {
            new AddEmployeeForm( driver.getDriver() )
                .insertUserName( employee.username )
                .then( () => addEmployeeForm.insertFirstName( employee.firstName ) )
                .then( () => addEmployeeForm.insertLastName( employee.lastName ) )
                .then( () => addEmployeeForm.clickCreateEmployee() )
                .then( () => employeePage.searchEmployee( employee ) );
        }, 750 )
    } );

1138190cookie-checkSind verschachtelte Versprechen in Node.js normal?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy