What an unhandled rejection means
A promise represents an operation that may finish successfully or fail. When it fails, the promise is rejected. If no rejection handler is attached when the browser checks that promise, the browser may report an unhandled promise rejection. A thrown error inside an async function also rejects the promise returned by that function, even if the code calling it looks like an ordinary function call.
For the examples below, you need a modern browser that supports async/await (ES2017) and its built-in developer tools. No libraries or server are required. Open a page you can use for testing, then open the developer tools Console. Browser wording varies, but an unhandled rejection commonly appears there as an error or warning with a stack trace.
Reproduce the problem and read the first clue
Paste this into the Console: async function loadSettings() { throw new Error("Settings unavailable"); } loadSettings(); The call starts the async function but ignores its returned promise. The function throws, so that promise rejects without a handler. This is a controlled example; it does not fetch anything or change the page.
Look for the error message and expand its stack trace if your tools offer one. The line containing throw tells you where the failure began. The line containing loadSettings() tells you where the rejected promise was left behind. In a larger application, those may be far apart. If the Console links to a source file, open it and inspect both the failing operation and its caller. A message that identifies the failure is useful, but finding the caller is what helps you decide where it should be handled.
You can also add a temporary browser-level listener before reproducing the failure: window.addEventListener("unhandledrejection", event => { console.error("Unhandled promise:", event.reason); }); The reason is the value used to reject the promise. It is often an Error, but code can reject with other values, such as a string. Use this listener as a diagnostic aid, not as a substitute for handling the promise where the operation is started. Avoid printing sensitive rejection details in production logs.
Trace the promise through its callers
Start at the reported failure and follow the returned promise outward. Search for the function call, then check whether its caller uses await, returns the promise, or attaches .catch(). If the caller does none of these, the failure may have nowhere useful to go. For example, runTask(); leaves its returned promise unattended, while runTask().catch(error => console.error(error)); handles a rejection at that call site.
Pay particular attention to event handlers and callbacks. An async click handler may throw after it begins, but the browser does not treat the event listener’s returned promise as an application error handler. Give it an explicit boundary: button.addEventListener("click", () => { runTask().catch(error => { showError(error); }); }); Here, showError stands for your own function that tells the user what went wrong. If you cannot recover locally, you can report the failure and leave the interface in a safe state.
Also inspect chains of promises. A .then() callback that throws creates a rejection in the promise returned by .then(). Handling only the original promise does not handle a later failure in the chain. Likewise, await Promise.all([firstTask(), secondTask()]) can throw if an input rejects; the surrounding async function still needs a catch boundary or a caller that handles its returned promise.
Put error handling where it can make a decision
Use try/catch when an async function can recover or add useful context: async function startPage() { try { const settings = await loadSettings(); displaySettings(settings); } catch (error) { showError(error); } } A caller must still handle a rejection if startPage() can throw for another reason. If a function cannot make a sensible decision about a failure, let the rejection propagate to a caller that can. Avoid adding empty catch blocks merely to silence the Console; they hide problems without fixing them.
After changing the code, repeat the action that originally failed. Check both that the unhandled-rejection report is gone and that the user sees an appropriate outcome. Then test the failure path deliberately—for example, by temporarily throwing an Error in a test function—and restore the normal code afterward. If a report remains, inspect the promise returned by the newest call or chain. An unhandled rejection is not always the original error; it may be a second failure introduced while trying to handle the first one.



