JavaScript-Versprechen - ablehnen gegen werfen

384

Ich habe mehrere Artikel zu diesem Thema gelesen, aber es ist mir immer noch nicht klar, ob es einen Unterschied zwischen dem Promise.rejectAuslösen eines Fehlers gibt. Zum Beispiel,

Verwenden von Promise.reject

return asyncIsPermitted()
    .then(function(result) {
        if (result === true) {
            return true;
        }
        else {
            return Promise.reject(new PermissionDenied());
        }
    });

Mit werfen

return asyncIsPermitted()
    .then(function(result) {
        if (result === true) {
            return true;
        }
        else {
            throw new PermissionDenied();
        }
    });

Ich bevorzuge es throweinfach zu verwenden , weil es kürzer ist, habe mich aber gefragt, ob es einen Vorteil gegenüber dem anderen gibt.

Naresh
quelle
9
Beide Methoden erzeugen genau die gleiche Antwort. Der .then()Handler fängt die ausgelöste Ausnahme ab und verwandelt sie automatisch in ein abgelehntes Versprechen. Da ich gelesen habe, dass ausgelöste Ausnahmen nicht besonders schnell ausgeführt werden können, würde ich vermuten, dass die Rückgabe des abgelehnten Versprechens möglicherweise etwas schneller ausgeführt werden kann, aber Sie müssten einen Test in mehreren modernen Browsern erstellen, wenn dies wichtig wäre. Ich persönlich benutze, throwweil ich die Lesbarkeit mag.
jfriend00
@webduvet nicht mit Versprechen - sie sind entwickelt, um mit Wurf zu arbeiten.
Joews
15
Ein Nachteil throwist, dass es nicht zu einem abgelehnten Versprechen führen würde, wenn es aus einem asynchronen Rückruf wie einem setTimeout heraus geworfen würde. jsfiddle.net/m07van33 @Blondie Ihre Antwort war korrekt.
Kevin B
@joews es bedeutet nicht, dass es gut ist;)
Webduvet
1
Ah, stimmt. Eine Klarstellung zu meinem Kommentar wäre also, "wenn er aus einem asynchronen Rückruf heraus geworfen wurde, der nicht versprochen wurde " . Ich wusste, dass es eine Ausnahme gab, ich konnte mich einfach nicht erinnern, was es war. Ich bevorzuge es auch, throw zu verwenden, nur weil ich finde, dass es besser lesbar ist und ich rejectes von meiner Parameterliste weglassen kann.
Kevin B

Antworten:

344

Es gibt keinen Vorteil, einen gegenüber dem anderen zu verwenden, aber es gibt einen bestimmten Fall, in dem dies thrownicht funktioniert. Diese Fälle können jedoch behoben werden.

Jedes Mal, wenn Sie sich innerhalb eines Rückrufs befinden, können Sie diesen verwenden throw. Wenn Sie sich jedoch in einem anderen asynchronen Rückruf befinden, müssen Sie verwenden reject.

Dies löst beispielsweise nicht den Fang aus:

new Promise(function() {
  setTimeout(function() {
    throw 'or nah';
    // return Promise.reject('or nah'); also won't work
  }, 1000);
}).catch(function(e) {
  console.log(e); // doesn't happen
});

Stattdessen bleibt Ihnen ein ungelöstes Versprechen und eine nicht erfasste Ausnahme. Dies ist ein Fall, in dem Sie stattdessen verwenden möchten reject. Sie können dies jedoch auf zwei Arten beheben.

  1. durch Verwendung der Ablehnungsfunktion des ursprünglichen Versprechens innerhalb des Timeouts:

new Promise(function(resolve, reject) {
  setTimeout(function() {
    reject('or nah');
  }, 1000);
}).catch(function(e) {
  console.log(e); // works!
});

  1. durch das Versprechen des Timeouts:

function timeout(duration) { // Thanks joews
  return new Promise(function(resolve) {
    setTimeout(resolve, duration);
  });
}

timeout(1000).then(function() {
  throw 'worky!';
  // return Promise.reject('worky'); also works
}).catch(function(e) {
  console.log(e); // 'worky!'
});

Kevin B.
quelle
54
Erwähnenswert ist, dass die Stellen in einem nicht versprochenen asynchronen Rückruf, die Sie nicht verwenden können throw error, auch nicht verwendet werden können return Promise.reject(err), was das OP von uns verlangt hat, zu vergleichen. Dies ist im Grunde der Grund, warum Sie keine asynchronen Rückrufe in Versprechen einfügen sollten. Versprechen Sie alles, was asynchron ist, und dann haben Sie diese Einschränkungen nicht.
jfriend00
9
"Wenn Sie sich jedoch in einer anderen Art von Rückruf befinden" sollte eigentlich "Wenn Sie sich jedoch in einer anderen Art von asynchronem Rückruf befinden" lauten . Rückrufe können synchron sein (z. B. mit Array#forEach) und mit diesen würde das Werfen in sie funktionieren.
Félix Saparelli
2
@ KevinB liest diese Zeilen "Es gibt einen speziellen Fall, in dem der Wurf nicht funktioniert." und "Jedes Mal, wenn Sie sich in einem Versprechen-Rückruf befinden, können Sie throw verwenden. Wenn Sie sich jedoch in einem anderen asynchronen Rückruf befinden, müssen Sie" ablehnen "verwenden." Ich habe das Gefühl, dass die Beispielausschnitte Fälle zeigen, in denen dies thrownicht funktioniert und stattdessen Promise.rejecteine bessere Wahl ist. Die Snippets sind jedoch von keiner dieser beiden Optionen betroffen und liefern unabhängig von Ihrer Auswahl das gleiche Ergebnis. Vermisse ich etwas
Anshul
2
Ja. Wenn Sie ein setTimeout verwenden, wird der catch nicht aufgerufen. Sie müssen das verwenden reject, das an den new Promise(fn)Rückruf übergeben wurde.
Kevin B
2
@ KevinB danke für den Aufenthalt. Das Beispiel von OP erwähnt, dass er speziell vergleichen wollte return Promise.reject()und throw. Er erwähnt den rejectRückruf im new Promise(function(resolve, reject))Konstrukt nicht. Während Ihre beiden Schnipsel zu Recht zeigen, wann Sie den Rückruf zum Auflösen verwenden sollten, war die Frage von OP nicht so.
Anshul
201

Eine weitere wichtige Tatsache ist, dass der reject() Kontrollfluss NICHT wie bei einer returnAnweisung beendet wird. Im Gegensatz dazu throwwird der Kontrollfluss beendet.

Beispiel:

new Promise((resolve, reject) => {
  throw "err";
  console.log("NEVER REACHED");
})
.then(() => console.log("RESOLVED"))
.catch(() => console.log("REJECTED"));

vs.

new Promise((resolve, reject) => {
  reject(); // resolve() behaves similarly
  console.log("ALWAYS REACHED"); // "REJECTED" will print AFTER this
})
.then(() => console.log("RESOLVED"))
.catch(() => console.log("REJECTED"));

lukyer
quelle
51
Nun, der Punkt ist richtig, aber der Vergleich ist schwierig. Denn normalerweise sollten Sie Ihr abgelehntes Versprechen schriftlich zurückgeben return reject(), damit die nächste Zeile nicht ausgeführt wird.
AZ.
7
Warum sollten Sie es zurückgeben wollen?
lukyer
31
In diesem Fall return reject()handelt es sich lediglich um eine Abkürzung für das reject(); returnheißt, Sie möchten den Fluss beenden. Der Rückgabewert des Executors (die an übergebene Funktion new Promise) wird nicht verwendet, daher ist dies sicher.
Félix Saparelli
47

Ja, der größte Unterschied besteht darin, dass " Zurückweisen" eine Rückruffunktion ist, die ausgeführt wird, nachdem das Versprechen abgelehnt wurde, während " Werfen" nicht asynchron verwendet werden kann. Wenn Sie die Option "Zurückweisen" gewählt haben, wird Ihr Code weiterhin normal asynchron ausgeführt, während " throw" die Ausführung der Resolver-Funktion priorisiert (diese Funktion wird sofort ausgeführt).

Ein Beispiel, das mir bei der Klärung des Problems geholfen hat, war, dass Sie eine Timeout-Funktion mit Ablehnen festlegen können, zum Beispiel:

new Promise(_, reject) {
 setTimeout(reject, 3000);
});

Das obige könnte nicht mit Wurf geschrieben werden.

In Ihrem kleinen Beispiel ist der Unterschied nicht zu unterscheiden, aber wenn Sie sich mit einem komplizierteren asynchronen Konzept befassen, kann der Unterschied zwischen beiden drastisch sein.

Blondie
quelle
1
Das klingt nach einem Schlüsselkonzept, aber ich verstehe es nicht so, wie es geschrieben steht. Für Promises immer noch zu neu, denke ich.
David Spector
43

TLDR: Eine Funktion ist schwer zu verwenden, wenn sie manchmal ein Versprechen zurückgibt und manchmal eine Ausnahme auslöst . Ziehen Sie es vor, beim Schreiben einer asynchronen Funktion einen Fehler zu melden, indem Sie ein abgelehntes Versprechen zurückgeben

Ihr spezielles Beispiel verschleiert einige wichtige Unterschiede zwischen ihnen:

Da Sie die Fehlerbehandlung sind innerhalb eines Versprechens Kette, erhalten geworfen Ausnahmen automatisch umgewandelt abgelehnt Versprechen. Dies mag erklären, warum sie austauschbar zu sein scheinen - sie sind es nicht.

Betrachten Sie die folgende Situation:

checkCredentials = () => {
    let idToken = localStorage.getItem('some token');
    if ( idToken ) {
      return fetch(`https://someValidateEndpoint`, {
        headers: {
          Authorization: `Bearer ${idToken}`
        }
      })
    } else {
      throw new Error('No Token Found In Local Storage')
    }
  }

Dies wäre ein Anti-Pattern, da Sie dann sowohl asynchrone als auch Synchronisierungsfehlerfälle unterstützen müssten. Es könnte ungefähr so ​​aussehen:

try {
  function onFulfilled() { ... do the rest of your logic }
  function onRejected() { // handle async failure - like network timeout }
  checkCredentials(x).then(onFulfilled, onRejected);
} catch (e) {
  // Error('No Token Found In Local Storage')
  // handle synchronous failure
} 

Nicht gut und genau hier kommt Promise.reject(im globalen Bereich verfügbar) zur Rettung und unterscheidet sich effektiv von throw. Der Refaktor wird nun:

checkCredentials = () => {
  let idToken = localStorage.getItem('some_token');
  if (!idToken) {
    return Promise.reject('No Token Found In Local Storage')
  }
  return fetch(`https://someValidateEndpoint`, {
    headers: {
      Authorization: `Bearer ${idToken}`
    }
  })
}

Auf diese Weise können Sie jetzt nur eine catch()für Netzwerkfehler und die synchrone Fehlerprüfung auf fehlende Token verwenden:

checkCredentials()
      .catch((error) => if ( error == 'No Token' ) {
      // do no token modal
      } else if ( error === 400 ) {
      // do not authorized modal. etc.
      }
Maxwell
quelle
1
Das Beispiel von Op gibt jedoch immer ein Versprechen zurück. Die Frage bezieht sich darauf, ob Sie ein abgelehntes Versprechen verwenden sollten Promise.rejectoder throwwann Sie es zurückgeben möchten (ein Versprechen, das zum nächsten springt .catch()).
Marcos Pereira
@maxwell - Ich mag dich Beispiel. In der gleichen Zeit, wenn Sie beim Abrufen einen Fang hinzufügen und darin die Ausnahme auslösen, können Sie sicher versuchen, ... fangen ... Es gibt keine perfekte Welt für den Ausnahmefluss, aber ich denke, dass Sie eine verwenden Ein einzelnes Muster ist sinnvoll, und das Kombinieren der Muster ist nicht sicher (ausgerichtet auf Ihr Muster im Vergleich zur Anti-Muster-Analogie).
user3053247
1
Ausgezeichnete Antwort, aber ich finde hier einen Fehler - dieses Muster setzt voraus, dass alle Fehler durch die Rückgabe eines Promise.reject behandelt werden - was passiert mit all den unerwarteten Fehlern, die einfach von checkCredentials () ausgelöst werden könnten?
Chenop
1
Ja, Sie haben Recht @chenop - um diese unerwarteten Fehler zu fangen, müssten Sie noch versuchen / fangen
Maxwell
Ich verstehe den Fall von @ maxwell nicht. Könnten Sie es nicht einfach so strukturieren checkCredentials(x).then(onFulfilled).catch(e) {}und catchsowohl den Ablehnungsfall als auch den ausgelösten Fehlerfall behandeln?
Ben Wheeler
5

Ein Beispiel zum Ausprobieren. Ändern Sie einfach isVersionThrow in false, um Reject anstelle von Throw zu verwenden.

const isVersionThrow = true

class TestClass {
  async testFunction () {
    if (isVersionThrow) {
      console.log('Throw version')
      throw new Error('Fail!')
    } else {
      console.log('Reject version')
      return new Promise((resolve, reject) => {
        reject(new Error('Fail!'))
      })
    }
  }
}

const test = async () => {
  const test = new TestClass()
  try {
    var response = await test.testFunction()
    return response 
  } catch (error) {
    console.log('ERROR RETURNED')
    throw error 
  }  
}

test()
.then(result => {
  console.log('result: ' + result)
})
.catch(error => {
  console.log('error: ' + error)
})

Chris Livdahl
quelle