Muss ich nach frühzeitiger Lösung / Ablehnung zurückkehren?

262

Angenommen, ich habe den folgenden Code.

function divide(numerator, denominator) {
 return new Promise((resolve, reject) => {

  if(denominator === 0){
   reject("Cannot divide by 0");
   return; //superfluous?
  }

  resolve(numerator / denominator);

 });
}

Wenn mein Ziel darin besteht, rejectfrühzeitig auszusteigen, sollte ich es mir dann auch zur Gewohnheit machen, returnsofort danach zu gehen?

Sam
quelle
5
Ja, wegen Run-to-Completion

Antworten:

370

Der returnZweck besteht darin, die Ausführung der Funktion nach der Zurückweisung zu beenden und die Ausführung des Codes danach zu verhindern.

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {

    if (denominator === 0) {
      reject("Cannot divide by 0");
      return; // The function execution ends here 
    }

    resolve(numerator / denominator);
  });
}

In diesem Fall wird die resolve(numerator / denominator);Ausführung verhindert , was nicht unbedingt erforderlich ist. Es ist jedoch weiterhin vorzuziehen, die Ausführung zu beenden, um eine mögliche Falle in der Zukunft zu verhindern. Darüber hinaus empfiehlt es sich, das unnötige Ausführen von Code zu verhindern.

Hintergrund

Ein Versprechen kann in einem von drei Zuständen sein:

  1. ausstehend - Ausgangszustand. Von ausstehend können wir in einen der anderen Staaten wechseln
  2. erfüllt - erfolgreicher Betrieb
  3. abgelehnt - fehlgeschlagener Vorgang

Wenn ein Versprechen erfüllt oder abgelehnt wird, bleibt es auf unbestimmte Zeit in diesem Zustand (erledigt). Die Ablehnung eines erfüllten Versprechens oder die Erfüllung eines abgelehnten Versprechens hat also keine Wirkung.

Dieses Beispiel-Snippet zeigt, dass das Versprechen zwar abgelehnt wurde, aber abgelehnt wurde.

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) {
      reject("Cannot divide by 0");
    }

    resolve(numerator / denominator);
  });
}

divide(5,0)
  .then((result) => console.log('result: ', result))
  .catch((error) => console.log('error: ', error));

Warum müssen wir also zurückkehren?

Obwohl wir einen festgelegten Versprechungsstatus nicht ändern können, wird die Ausführung des Restes der Funktion durch Ablehnen oder Auflösen nicht gestoppt. Die Funktion kann Code enthalten, der verwirrende Ergebnisse erzeugt. Beispielsweise:

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) {
      reject("Cannot divide by 0");
    }
    
    console.log('operation succeeded');

    resolve(numerator / denominator);
  });
}

divide(5, 0)
  .then((result) => console.log('result: ', result))
  .catch((error) => console.log('error: ', error));

Selbst wenn die Funktion derzeit keinen solchen Code enthält, entsteht eine mögliche zukünftige Falle. Ein zukünftiger Refactor ignoriert möglicherweise die Tatsache, dass der Code nach Ablehnung des Versprechens noch ausgeführt wird und schwer zu debuggen ist.

Beenden der Ausführung nach dem Auflösen / Ablehnen:

Dies ist Standard-JS-Kontrollflussmaterial.

  • Rückkehr nach dem resolve/ reject:

  • Rückgabe mit dem resolve/ reject- Da der Rückgabewert des Rückrufs ignoriert wird, können wir eine Zeile speichern, indem wir die Zurückweisungs- / Auflösungsanweisung zurückgeben:

  • Verwenden eines if / else-Blocks:

Ich bevorzuge eine der returnOptionen, da der Code flacher ist.

Ori Drori
quelle
28
Es ist erwähnenswert, dass sich der Code nicht anders verhält, wenn er vorhanden returnist oder nicht, da er nach dem Festlegen eines Versprechensstatus nicht mehr geändert werden kann, sodass ein Aufruf resolve()nach dem Aufruf reject()nur einige zusätzliche CPU-Zyklen verwendet. Ich selbst würde das returnnur unter dem Gesichtspunkt der Code-Sauberkeit und -Effizienz verwenden, aber es ist in diesem speziellen Beispiel nicht erforderlich.
jfriend00
1
Versuchen Sie es mit Promise.try(() => { })anstelle von neuem Versprechen und vermeiden Sie es, Anrufe aufzulösen / abzulehnen. Stattdessen könnten Sie einfach schreiben, dass return denominator === 0 ? throw 'Cannot divide by zero' : numerator / denominator; ich Promise.tryein Mittel benutze , um ein Versprechen zu starten und Versprechen zu eliminieren, die in problematische Try / Catch-Blöcke eingewickelt sind.
Kingdango
2
Es ist gut zu wissen, und ich mag das Muster. Doch zu diesem Zeitpunkt Promise.try ist eine Stufe 0 Vorschlag, so dass Sie nur mit einem verwenden können Shim oder durch ein Versprechen Bibliothek wie drossel oder Q. mit
Ori Drori
6
@ jfriend00 In diesem einfachen Beispiel verhält sich der Code natürlich nicht anders. Aber was wäre, wenn Sie danach Code hätten reject, der etwas Teueres bewirkt, wie die Verbindung zu Datenbanken oder API-Endpunkten? Dies ist alles unnötig und kostet Sie Geld und Ressourcen, insbesondere wenn Sie beispielsweise eine Verbindung zu einer AWS-Datenbank oder einem API-Gateway-Endpunkt herstellen. In diesem Fall würden Sie definitiv eine Rückgabe verwenden, um zu verhindern, dass unnötiger Code ausgeführt wird.
Jake Wilson
3
@JakeWilson - Natürlich ist das nur ein normaler Code-Fluss in Javascript und hat überhaupt nichts mit Versprechungen zu tun. Wenn Sie mit der Verarbeitung der Funktion fertig sind und nicht mehr Code im aktuellen Codepfad ausführen möchten, fügen Sie a ein return.
jfriend00
37

Eine übliche Redewendung, die Ihre Tasse Tee sein kann oder nicht, besteht darin, das returnmit dem zu kombinieren reject, um gleichzeitig das Versprechen abzulehnen und die Funktion zu verlassen, so dass der Rest der Funktion einschließlich des resolvenicht ausgeführt wird. Wenn Ihnen dieser Stil gefällt, kann Ihr Code dadurch etwas kompakter werden.

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) return reject("Cannot divide by 0");
                           ^^^^^^^^^^^^^^
    resolve(numerator / denominator);
  });
}

Dies funktioniert gut , weil das Versprechen Konstruktor nichts mit einem Rückgabewert der Fall ist, und in jedem Fall resolveund rejectRückkehr nichts.

Dieselbe Redewendung kann mit dem in einer anderen Antwort gezeigten Rückrufstil verwendet werden:

function divide(nom, denom, cb){
  if(denom === 0) return cb(Error("Cannot divide by zero"));
                  ^^^^^^^^^
  cb(null, nom / denom);
} 

Auch dies funktioniert einwandfrei, da die anrufende Person dividenicht erwartet, dass sie etwas zurückgibt, und nichts mit dem Rückgabewert tut.


quelle
6
Ich mag das nicht. Dies gibt die Vorstellung, dass Sie etwas zurückgeben, was Sie tatsächlich nicht sind. Sie rufen die Ablehnungsfunktion auf und beenden dann mit return die Funktionsausführung. Halten Sie sie in getrennten Zeilen, was Sie tun, wird nur Menschen verwirren. Die Lesbarkeit des Codes ist König.
K - Die Toxizität in SO nimmt zu.
7
@KarlMorrison Sie geben tatsächlich "etwas" zurück, ein abgelehntes Versprechen. Ich denke, dass "Begriff", von dem Sie sprechen, sehr persönlich ist. Es ist nichts Falsches daran, einen rejectStatus zurückzugeben
Frondor
5
@Frondor Ich glaube nicht, dass du verstanden hast, was ich geschrieben habe. Natürlich verstehen Sie und ich, dass nichts passiert, wenn Sie eine Ablehnung in derselben Zeile zurückgeben. Aber was ist mit Entwicklern, die es nicht so gewohnt sind, dass JavaScript in ein Projekt kommt? Diese Art der Programmierung verringert die Lesbarkeit für solche Personen. Das heutige JavaScript-Ökosystem ist ein Chaos genug, und Menschen, die diese Art von Praktiken verbreiten, werden es nur noch schlimmer machen. Das ist schlechte Praxis.
K - Die Toxizität in SO nimmt zu.
1
@KarlMorrison Persönliche Meinungen! = Schlechte Praxis. Es würde wahrscheinlich einem neuen Javascript-Entwickler helfen, zu verstehen, was mit der Rückkehr los ist.
Toby Caulk
1
@TobyCaulk Wenn die Leute lernen müssen, was die Rückkehr bewirkt, sollten sie nicht mit Versprechungen herumspielen, sondern grundlegende Programmierung lernen.
K - Die Toxizität in SO nimmt zu.
10

Technisch wird es hier nicht benötigt 1 - weil ein Versprechen ausschließlich und nur einmal gelöst oder abgelehnt werden kann . Das erste Promise-Ergebnis gewinnt und jedes nachfolgende Ergebnis wird ignoriert. Dies unterscheidet sich von Rückrufen im Knotenstil.

Abgesehen davon ist es eine gute saubere Praxis , sicherzustellen, dass genau eine aufgerufen wird, wenn dies praktikabel ist, und zwar in diesem Fall, da keine weitere asynchrone / verzögerte Verarbeitung erfolgt. Die Entscheidung, "früh zurückzukehren", unterscheidet sich nicht von der Beendigung einer Funktion nach Abschluss ihrer Arbeit - im Gegensatz zur Fortsetzung einer nicht verwandten oder unnötigen Verarbeitung.

Die Rückkehr zum richtigen Zeitpunkt (oder die anderweitige Verwendung von Bedingungen, um die Ausführung des "anderen" Falls zu vermeiden) verringert die Wahrscheinlichkeit, dass Code versehentlich in einem ungültigen Zustand ausgeführt wird oder unerwünschte Nebenwirkungen auftreten. und als solches macht es Code weniger anfällig für "unerwartetes Brechen".


1 Diese technische Antwort hängt auch davon ab, dass in diesem Fall der Code nach der "Rückgabe", falls er weggelassen wird, keine Nebenwirkungen hervorruft. JavaScript teilt sich glücklich durch Null und gibt entweder + Infinity / -Infinity oder NaN zurück.

user2864740
quelle
Schöne Fußnote !!
HankCa
9

Wenn Sie nach einer Auflösung / Ablehnung nicht "zurückkehren", können schlimme Dinge (wie eine Seitenumleitung) passieren, nachdem Sie beabsichtigt haben, dass sie beendet werden. Quelle: Ich bin darauf gestoßen.

Benjamin H.
quelle
6
+1 für das Beispiel. Ich hatte ein Problem, bei dem mein Programm mehr als 100 ungültige Datenbankabfragen durchführte, und ich konnte nicht herausfinden, warum. Es stellte sich heraus, dass ich nach einer Ablehnung nicht "zurückgekehrt" bin. Es ist ein kleiner Fehler, aber ich habe meine Lektion gelernt.
AdamInTheOculus
8

Die Antwort von Ori erklärt bereits, dass dies nicht notwendig ist, returnaber eine gute Praxis ist. Beachten Sie, dass der Versprechen-Konstruktor ausfallsicher ist, sodass er später im Pfad übergebene ausgelöste Ausnahmen ignoriert. Im Wesentlichen treten Nebenwirkungen auf, die Sie nicht leicht beobachten können.

Beachten Sie, dass returnFrühbucher auch bei Rückrufen sehr häufig sind:

function divide(nom, denom, cb){
     if(denom === 0){
         cb(Error("Cannot divide by zero");
         return; // unlike with promises, missing the return here is a mistake
     }
     cb(null, nom / denom); // this will divide by zero. Since it's a callback.
} 

Während es sich bei Versprechungen um eine gute Praxis handelt, ist dies bei Rückrufen erforderlich . Einige Hinweise zu Ihrem Code:

  • Ihr Anwendungsfall ist hypothetisch. Verwenden Sie keine Versprechen mit synchronen Aktionen.
  • Der Versprechen-Konstruktor ignoriert Rückgabewerte. Einige Bibliotheken warnen, wenn Sie einen nicht undefinierten Wert zurückgeben, um Sie vor dem Fehler zu warnen, dorthin zurückzukehren. Die meisten sind nicht so schlau.
  • Der Versprechen-Konstruktor ist sicher, er wandelt Ausnahmen in Ablehnungen um, aber wie andere betont haben, wird ein Versprechen einmal aufgelöst.
Benjamin Gruenbaum
quelle
4

In vielen Fällen ist es möglich, Parameter separat zu validieren und ein abgelehntes Versprechen sofort mit Promise.reject (Grund) zurückzugeben .

function divide2(numerator, denominator) {
  if (denominator === 0) {
    return Promise.reject("Cannot divide by 0");
  }
  
  return new Promise((resolve, reject) => {
    resolve(numerator / denominator);
  });
}


divide2(4, 0).then((result) => console.log(result), (error) => console.log(error));

Dorad
quelle