Was ist das explizite Versprechenskonstruktions-Antimuster und wie vermeide ich es?

516

Ich habe Code geschrieben, der etwas tut, das so aussieht:

function getStuffDone(param) {           | function getStuffDone(param) {
    var d = Q.defer(); /* or $q.defer */ |     return new Promise(function(resolve, reject) {
    // or = new $.Deferred() etc.        |     // using a promise constructor
    myPromiseFn(param+1)                 |         myPromiseFn(param+1)
    .then(function(val) { /* or .done */ |         .then(function(val) {
        d.resolve(val);                  |             resolve(val);
    }).catch(function(err) { /* .fail */ |         }).catch(function(err) {
        d.reject(err);                   |             reject(err);
    });                                  |         });
    return d.promise; /* or promise() */ |     });
}                                        | }

Jemand hat mir gesagt, dass dies als " verzögertes Antimuster " bzw. " PromiseKonstruktor-Antimuster " bezeichnet wird. Was ist schlecht an diesem Code und warum wird dies als Antimuster bezeichnet ?

Benjamin Gruenbaum
quelle
Kann ich bestätigen, dass das Entfernen (im Kontext des Beispiels rechts, nicht links) den getStuffDoneFunktionsumbruch entfernt und nur das Promise-Literal verwendet?
Der Dembinski
1
oder ist der catchBlock im getStuffDoneWrapper das Antimuster?
Der Dembinski
1
Zumindest für das native PromiseBeispiel haben Sie auch unnötige Funktions-Wrapper für die .thenund .catchHandler (dh es könnte einfach sein .then(resolve).catch(reject)). Ein perfekter Sturm von Anti-Mustern.
Noah Freitas
6
@NoahFreitas dieser Code ist für didaktische Zwecke so geschrieben. Ich habe diese Frage und Antwort geschrieben, um Menschen zu helfen, die auf dieses Problem stoßen, nachdem sie viel Code gelesen haben, der so aussieht :)
Benjamin Gruenbaum
Unter stackoverflow.com/questions/57661537/… erfahren Sie, wie Sie nicht nur die explizite Promise-Konstruktion, sondern auch die Verwendung einer globalen Variablen eliminieren.
David Spector

Antworten:

357

Das von Esailija geprägte verzögerte Antimuster (jetzt explizit konstruiertes Anti-Muster) ist ein weit verbreitetes Anti-Muster-Volk, das neu in Versprechungen ist. Ich habe es selbst gemacht, als ich Versprechen zum ersten Mal verwendet habe. Das Problem mit dem obigen Code ist, dass die Tatsache, die Kette verspricht, nicht ausgenutzt wird.

Versprechen können verkettet werden .thenund Sie können Versprechen direkt zurückgeben. Ihr Code in getStuffDonekann wie folgt umgeschrieben werden:

function getStuffDone(param){
    return myPromiseFn(param+1); // much nicer, right?
}

Bei Versprechungen geht es darum, asynchronen Code lesbarer zu machen und sich wie synchroner Code zu verhalten, ohne diese Tatsache zu verbergen. Versprechen stellen eine Abstraktion über einen Wert einer einmaligen Operation dar, sie abstrahieren den Begriff einer Aussage oder eines Ausdrucks in einer Programmiersprache.

Sie sollten verzögerte Objekte nur verwenden, wenn Sie eine API in Versprechen konvertieren und dies nicht automatisch tun können oder wenn Sie Aggregationsfunktionen schreiben, die auf diese Weise einfacher ausgedrückt werden.

Zitat Esailija:

Dies ist das häufigste Anti-Muster. Es ist leicht, darauf hereinzufallen, wenn Sie Versprechen nicht wirklich verstehen und sie als verherrlichte Ereignisemitter oder Rückrufdienstprogramme betrachten. Fassen wir zusammen: Bei Versprechungen geht es darum, dass asynchroner Code die meisten verlorenen Eigenschaften von synchronem Code wie flache Einrückungen und einen Ausnahmekanal beibehält.

Benjamin Gruenbaum
quelle
@BenjaminGruenbaum: Ich bin zuversichtlich, dass ich dafür aufgeschobene Daten verwenden kann, sodass keine neue Frage erforderlich ist. Ich dachte nur, es sei ein Anwendungsfall, den Sie in Ihrer Antwort vermisst haben. Was ich tue, scheint eher das Gegenteil von Aggregation zu sein, nicht wahr?
Mhelvens
1
@mhelvens Wenn Sie eine Nicht-Rückruf-API manuell in eine Versprechen-API aufteilen, die zum Teil "Konvertieren einer Rückruf-API in Versprechen" passt. Bei dem Antipattern geht es darum, ein Versprechen ohne guten Grund in ein anderes Versprechen zu verpacken. Sie verpacken zunächst kein Versprechen, sodass es hier nicht gilt.
Benjamin Gruenbaum
@BenjaminGruenbaum: Ah, ich dachte, dass die Zurückstellung selbst als Anti-Muster angesehen wurde, was damit zu tun hat, dass Bluebird sie ablehnt, und Sie erwähnen "Konvertieren einer API in Versprechen" (was auch ein Fall ist, in dem ein Versprechen zunächst nicht verpackt wird).
Mhelvens
@mhelvens Ich denke, das überschüssige verzögerte Anti-Muster wäre genauer für das, was es tatsächlich tut. Bluebird hat die .defer()API in den neueren (und sicheren) Versprechenskonstruktor verworfen, sie hat den Gedanken, Versprechungen zu konstruieren, nicht (in keiner Weise) missbilligt :)
Benjamin Gruenbaum
1
Vielen Dank an Roamer-1888. Ihre Referenz hat mir geholfen, endlich herauszufinden, was mein Problem war. Sieht so aus, als hätte ich verschachtelte (nicht zurückgegebene) Versprechen erstellt, ohne es zu merken.
Ghuroo
134

Was stimmt damit nicht?

Aber das Muster funktioniert!

Du Glückspilz. Leider ist dies wahrscheinlich nicht der Fall, da Sie wahrscheinlich einen Randfall vergessen haben. In mehr als der Hälfte der Fälle, die ich gesehen habe, hat der Autor vergessen, sich um den Fehlerbehandler zu kümmern:

return new Promise(function(resolve) {
    getOtherPromise().then(function(result) {
        resolve(result.property.example);
    });
})

Wenn das andere Versprechen abgelehnt wird, geschieht dies unbemerkt, anstatt an das neue Versprechen weitergegeben zu werden (wo es behandelt wird) - und das neue Versprechen bleibt für immer ausstehend, was zu Undichtigkeiten führen kann.

Dasselbe passiert, wenn Ihr Rückrufcode einen Fehler verursacht - z. B. wenn resultkeine vorhanden ist propertyund eine Ausnahme ausgelöst wird. Das würde unbehandelt bleiben und das neue Versprechen ungelöst lassen.

Im Gegensatz dazu .then()kümmert sich die Verwendung automatisch um beide Szenarien und lehnt das neue Versprechen ab, wenn ein Fehler auftritt:

 return getOtherPromise().then(function(result) {
     return result.property.example;
 })

Das verzögerte Antimuster ist nicht nur umständlich, sondern auch fehleranfällig . Die Verwendung .then()zur Verkettung ist viel sicherer.

Aber ich habe alles erledigt!

"Ja wirklich?" Gut. Dies ist jedoch sehr detailliert und umfangreich, insbesondere wenn Sie eine Versprechensbibliothek verwenden, die andere Funktionen wie Stornierung oder Nachrichtenübermittlung unterstützt. Oder vielleicht wird es in Zukunft so sein, oder Sie möchten Ihre Bibliothek gegen eine bessere austauschen? Sie möchten Ihren Code dafür nicht neu schreiben.

Die Methoden ( then) der Bibliotheken unterstützen nicht nur alle Funktionen von Haus aus, sondern verfügen möglicherweise auch über bestimmte Optimierungen. Wenn Sie sie verwenden, wird Ihr Code wahrscheinlich schneller oder kann zumindest durch zukünftige Überarbeitungen der Bibliothek optimiert werden.

Wie vermeide ich das?

Wenn Sie also manuell ein Promiseoder Deferredbereits vorhandene Versprechen erstellen , überprüfen Sie zuerst die Bibliotheks-API . Das verzögerte Antimuster wird oft von Menschen angewendet, die Versprechen [nur] als Beobachtermuster betrachten - aber Versprechen sind mehr als Rückrufe : Sie sollen zusammensetzbar sein. Jede anständige Bibliothek verfügt über viele benutzerfreundliche Funktionen für die Zusammenstellung von Versprechungen auf jede denkbare Weise, die sich um all die einfachen Dinge kümmern, mit denen Sie sich nicht befassen möchten.

Wenn Sie festgestellt haben, dass Sie einige Versprechen auf eine neue Art und Weise verfassen müssen, die von einer vorhandenen Hilfsfunktion nicht unterstützt wird, sollte das Schreiben einer eigenen Funktion mit unvermeidbaren Verzögerungen Ihre letzte Option sein. Wechseln Sie zu einer funktionsfähigeren Bibliothek und / oder melden Sie einen Fehler in Ihrer aktuellen Bibliothek. Der Betreuer sollte in der Lage sein, die Zusammensetzung aus vorhandenen Funktionen abzuleiten, eine neue Hilfsfunktion für Sie zu implementieren und / oder dabei zu helfen, die zu behandelnden Randfälle zu identifizieren.

Bergi
quelle
Gibt es andere Beispiele als eine Funktion setTimeout, bei der der Konstruktor verwendet werden könnte, aber nicht als "Versprechungskonstruktor anitpattern" betrachtet wird?
Gast271314
1
@ guest271314: Alles asynchron, was kein Versprechen zurückgibt. Obwohl Sie oft genug bessere Ergebnisse mit den engagierten Promisification-Helfern der Bibliotheken erzielen. Und stellen Sie sicher, dass Sie immer auf der untersten Ebene versprechen, damit es nicht " eine Funktion einschließlichsetTimeout " ist, sondern " die Funktion setTimeoutselbst ".
Bergi
"Und stellen Sie sicher, dass Sie immer auf der untersten Ebene versprechen, damit es nicht" eine Funktion einschließlich setTimeout"ist, sondern" die Funktion setTimeoutselbst "." Kann man beschreiben, mit Unterschieden in Verbindung bringen, zwischen den beiden?
Gast271314
@ guest271314 Eine Funktion, die nur einen Aufruf von enthält, unterscheidet setTimeoutsich deutlich von der Funktion setTimeoutselbst , nicht wahr ?
Bergi
4
Ich denke, eine der wichtigen Lektionen hier, die bisher nicht klar formuliert wurde, ist, dass ein Versprechen und sein verkettetes "Dann" eine asynchrone Operation darstellt: Die anfängliche Operation befindet sich im Promise-Konstruktor und der endgültige Endpunkt im "Versprechen". dann 'Funktion. Wenn Sie also eine Synchronisierungsoperation gefolgt von einer Asynchronisierungsoperation haben, fügen Sie das Synchronisierungsmaterial in das Versprechen ein. Wenn Sie eine asynchrone Operation gefolgt von einer Synchronisierung haben, setzen Sie das Synchronisierungsmaterial in das 'Dann'. Geben Sie im ersten Fall das ursprüngliche Versprechen zurück. Im zweiten Fall geben Sie die Promise / then-Kette zurück (die auch ein Promise ist).
David Spector