Sauberere Async-Ketten mit Promise.try schreiben
Wenn Sie jemals eine Promise-Kette geschrieben haben, die mit einer Funktion beginnt, die möglicherweise synchron oder möglicherweise asynchron ist, sind Sie wahrscheinlich auf ein unangenehmes Problem gestoßen: Wo platzieren Sie Ihr .catch()?
Synchrone Fehler, die geworfen werden, bevor ein Promise zurückgegeben wird, werden von .catch() nicht abgefangen, es sei denn, Sie befinden sich bereits in einem Promise-Kontext. Promise.try() löst dies elegant, indem es Ihnen einen einzigen, konsistenten Einstiegspunkt für jede Promise-Kette bietet – unabhängig davon, ob die aufgerufene Funktion synchron, asynchron oder irgendwo dazwischen ist.
Wichtigste Erkenntnisse
- Synchrone Throws innerhalb von Funktionen, die manchmal Promises zurückgeben, können
.catch()vollständig entkommen und zu unbehandelten Exceptions führen. Promise.try()führt eine Funktion sofort aus und verpackt sowohl synchrone Rückgabewerte als auch synchrone Throws in ein ordnungsgemäßes Promise, wodurch Sie einen einheitlichen Fehlerbehandlungspfad erhalten.- Im Gegensatz zu
Promise.resolve(fn())fängt es synchrone Fehler ab. Im Gegensatz zuPromise.resolve().then(fn)wird es sofort ausgeführt, ohne auf einen Microtask zu verschieben. - Es eignet sich am besten für
.then()-basierte Ketten mit gemischten sync/async-Einstiegspunkten – nicht als Ersatz fürasync/await.
Das Problem: Synchrone Fehler, die Ihrer .catch()-Kette entkommen
Betrachten Sie einen Data-Loader, der je nach Bedingungen synchron aus einem Cache liest oder asynchron von einer API abruft:
function loadData(key) {
const cached = getFromCache(key) // may throw synchronously
if (cached) return cached
return fetch(`/api/data/${key}`).then(res => res.json())
}
loadData('user-1')
.then(data => render(data))
.catch(err => handleError(err)) // ⚠️ Won't catch sync throws from loadData
Wenn getFromCache synchron einen Fehler wirft, wird dieser Fehler niemals von .catch() abgefangen. Der Throw erfolgt, bevor ein Promise existiert, sodass er der Kette vollständig entkommt und zu einer unbehandelten Exception wird.
Es gibt hier eine zweite Subtilität, die erwähnenswert ist: Wenn loadData einen gecachten Wert direkt zurückgibt (ein Non-Thenable), schlägt der Aufruf von .then() darauf ebenfalls fehl, da einfache Werte keine .then()-Methode haben. Diese Funktion ist grundsätzlich fragil – sie gibt in einem Zweig ein Promise und in einem anderen einen rohen Wert zurück. Promise.try() adressiert beide Probleme, indem es immer ein Promise erzeugt.
Wie Promise.try() dies behebt
Promise.try(fn) führt die bereitgestellte Funktion sofort aus und verpackt das Ergebnis in ein Promise. Wenn die Funktion einen einfachen Wert zurückgibt, wird dieser als Resolved-Wert verwendet. Wenn sie ein Promise zurückgibt, wird dieses Promise übernommen. Wenn sie synchron einen Fehler wirft, wird dieser Fehler in eine Rejection umgewandelt.
Dies gibt Ihnen einen einzigen Einstiegspunkt für die Behandlung sowohl synchroner als auch asynchroner Fehler über dasselbe .catch():
Promise.try(() => loadData('user-1'))
.then(data => render(data))
.catch(err => handleError(err)) // ✅ Catches both sync throws and async rejections
Keine Sonderfälle. Kein Wrapping in try/catch vor dem Start der Kette. Alles fließt wie erwartet durch .catch().
Discover how at OpenReplay.com.
Wie es sich von Promise.resolve().then(fn) und Promise.resolve(fn()) unterscheidet
Diese beiden Muster werden häufig als Workarounds verwendet, verhalten sich aber in wichtigen Punkten unterschiedlich.
Promise.resolve(fn()) ruft fn() sofort auf, außerhalb eines Promise-Kontexts. Ein synchroner Throw ist hier eine nicht abgefangene Exception, keine Rejection.
Promise.resolve().then(fn) verschiebt die Ausführung von fn auf einen Microtask. Das bedeutet, dass fn nicht sofort ausgeführt wird – was zu subtilen Timing-Problemen führen kann und das Verhalten weniger vorhersehbar macht, wenn Sie eine sofortige Ausführung benötigen.
Promise.try(fn) führt fn sofort aus und fängt jeden synchronen Throw als Rejection ab. Es ist das vorhersehbarste der drei Muster zum Starten von Promise-Ketten.
| Muster | Führt fn sofort aus | Fängt sync Throws ab |
|---|---|---|
Promise.resolve(fn()) | ✅ | ❌ |
Promise.resolve().then(fn) | ❌ | ✅ |
Promise.try(fn) | ✅ | ✅ |
Praktische Frontend-Anwendungsfälle
Promise.try() fügt sich natürlich in JavaScript-Async-Muster ein, bei denen das Verhalten von Laufzeitbedingungen abhängt:
Utility-Funktionen, die gecachte Daten synchron zurückgeben oder sie asynchron abrufen können:
Promise.try(() => getUserFromCacheOrAPI(userId))
.then(updateUI)
.catch(showErrorBanner)
Bedingte asynchrone Workflows, bei denen ein Validierungsschritt einen Fehler werfen könnte, bevor asynchrone Arbeit beginnt:
Promise.try(() => {
validateInput(formData) // throws if invalid
return submitForm(formData) // returns a promise
})
.then(handleSuccess)
.catch(handleError)
Browser-Support und Kompatibilität
Promise.try() wurde in ECMAScript 2025 (ES2025) aufgenommen und wird in Chrome 128+, Firefox 134+, Safari 18.2+ und Node.js 22.7.0+ unterstützt. Sie können die aktuelle Browser-Unterstützung auf Can I Use überprüfen, das den Implementierungsstatus in den wichtigsten Browsern und Laufzeitumgebungen verfolgt.
Für ältere Umgebungen können Sie es mit einem einfachen Wrapper polyfüllen:
Promise.try = Promise.try || function(fn) {
return new Promise(resolve => resolve(fn()))
}
Dies funktioniert, weil der new Promise-Executor synchron ausgeführt wird, sodass fn() sofort aufgerufen wird. Wenn fn() einen Fehler wirft, fängt der Promise-Konstruktor ihn ab und verwandelt ihn in eine Rejection. Wenn fn() ein Thenable zurückgibt, übernimmt resolve es.
Fazit
Promise.try() ist kein Ersatz für async/await. Es ist ein kleines, fokussiertes Werkzeug für eine spezifische Situation: das Starten einer Promise-Kette, wenn der Einstiegspunkt möglicherweise synchron einen Fehler wirft oder eine Mischung aus Werten und Promises zurückgibt.
Wenn Sie sich bereits in einer async-Funktion befinden, behandelt ein try/catch-Block beide Fälle natürlich. Aber wenn Sie mit .then()-Ketten arbeiten – insbesondere bei Utility-Funktionen oder Data-Loadern mit bedingter Logik – hält Promise.try() Ihre Fehlerbehandlung konsistent und Ihre Ketten sauber.
FAQs
Ja. Wenn die Funktion, die Sie an Promise.try() übergeben, async ist, gibt sie ein Promise zurück, und Promise.try() übernimmt dieses Promise. Es funktioniert genauso wie bei jeder anderen Promise-zurückgebenden Funktion. Der Hauptvorteil von Promise.try() liegt bei Funktionen, die möglicherweise überhaupt kein Promise zurückgeben oder die einen Fehler werfen könnten, bevor sie eines zurückgeben.
Nein. Innerhalb einer async-Funktion fängt ein Standard-try/catch-Block bereits sowohl synchrone Throws als auch erwartete Rejections ab. Promise.try() ist für .then()-basierte Ketten konzipiert, bei denen Sie einen sicheren Einstiegspunkt benötigen. Wenn Sie bereits async/await verwenden, benötigen Sie wahrscheinlich kein Promise.try().
Das gängige Polyfill mit new Promise(resolve => resolve(fn())) ist funktional äquivalent zur nativen Implementierung. Es führt fn sofort aus, fängt synchrone Throws über den Promise-Konstruktor ab und übernimmt Thenables durch resolve. Es ist sicher für den Produktionseinsatz in Umgebungen, die keine native Unterstützung haben.
Promise.resolve(fn()) ruft fn außerhalb eines Promise-Kontexts auf, sodass synchrone Throws zu nicht abgefangenen Exceptions werden. Promise.resolve().then(fn) fängt Throws ab, verschiebt die Ausführung aber auf einen Microtask, was bedeutet, dass fn nicht sofort ausgeführt wird. Promise.try(fn) ist das einzige Muster, das sowohl fn sofort ausführt als auch synchrone Fehler als Rejections abfängt.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.