DriversRecommendedOutdated drivers can make a good PC feel brokenScan driver issues before chasing fixes manually.Scan NowOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix Now×
Skip to content
browser automation

How to Fix Unhandled Promise Rejections in Puppeteer (Node.js)

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

An unhandled Puppeteer rejection is fixed where the failed promise is created: await the operation inside a meaningful try…catch, return the promise to its caller, or attach .catch() to the exact chain. Then inspect the rejection reason and determine whether the failure came from Node code, JavaScript running in the page, or the browser protocol. A process-level listener is useful for diagnosis, but it does not repair the operation.

What an “unhandled rejection” means in Puppeteer

Puppeteer calls such as browser.newPage(), page.goto(), and page.click() return promises. Node.js emits unhandledRejection when a promise is rejected and no error handler is attached within a turn of the event loop. The event provides the rejection reason and the promise. If a handler is attached later, Node can emit rejectionHandled.

This is a Node runtime condition, not a Puppeteer-specific exception class. A rejection may also be produced by a promise farther down a chain: a callback passed to .then() can throw, creating a new rejected promise. That resulting promise must itself be returned or handled.

The reliable repair pattern

Await operations inside an async workflow

Put related browser work in an async function and await every promise that can fail. Handle the workflow at a boundary where you can log, retry, or report the error.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const puppeteer = require('puppeteer');

async function run() {
  const browser = await puppeteer.launch();
  try {
    const page = await browser.newPage();
    await page.goto('https://example.com', {waitUntil: 'networkidle2'});
    await page.screenshot({path: 'example.png', fullPage: true});
  } finally {
    await browser.close();
  }
}

run().catch(error => {
  console.error('Puppeteer task failed:', error);
  process.exitCode = 1;
});

The top-level run().catch() owns failures that escape the workflow. The finally block attempts cleanup even when navigation or capture fails. In production, decide how to handle a cleanup failure so it does not hide the original error.

Use a catch on a promise chain

If you use promise chaining instead of async/await, return nested promises and attach .catch() to the chain that you intend to observe.

function capture() {
  return puppeteer.launch()
    .then(browser => browser.newPage().then(page => ({browser, page})))
    .then(({browser, page}) => page.goto('https://example.com')
      .then(() => page.screenshot({path: 'example.png'}))
      .finally(() => browser.close()));
}

capture().catch(error => {
  console.error('Capture failed:', error);
});

A common bug is starting an asynchronous callback without returning it:

// The outer chain finishes before the inner rejection is attached.
items.forEach(item => {
  doAsyncWork(item); // rejection can become unhandled
});

// Prefer a loop when work must be awaited.
for (const item of items) {
  await doAsyncWork(item);
}

For independent work, use Promise.all() and catch its result. If one task failing should not cancel the others, use Promise.allSettled() and inspect each status.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Find the promise that lost its handler

  1. Log the complete reason. Preserve the error object and stack; do not log only error.message.
  2. Search every promise boundary. Check Puppeteer calls, .then() callbacks, timers, array iteration methods, and event listeners. An outer catch cannot observe work that was detached.
  3. Choose a policy. At the local boundary, decide whether to retry, skip, close the browser, or fail the job. Do not silently swallow the rejection.
  4. Verify cleanup. Browser shutdown is asynchronous and can reject too. Keep cleanup in finally and preserve the primary failure when reporting both errors.

Observing process-level events

During diagnosis, you can record unhandled rejections:

process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled rejection:', reason);
  console.error('Promise:', promise);
});

process.on('rejectionHandled', promise => {
  console.warn('A rejection gained a handler later:', promise);
});

These listeners show what escaped normal handling; they do not make the failed Puppeteer operation successful. Node documents that an unhandled rejection that remains unhandled can be raised as an uncaught exception, and command-line policy can be changed with --unhandled-rejections. Treat global hooks as monitoring, not normal recovery.

Separate Node errors from page errors

Puppeteer has two JavaScript environments: your Node “server” code and the page’s browser “client” code. A page script throwing does not automatically appear as a Node promise rejection.

Relay browser console output

page.on('console', message => {
  console.log(`[page ${message.type()}]`, message.text());
});

This listener forwards messages emitted by console.log, console.error, and related page APIs to your Node logs.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Listen for page exceptions

page.on('pageerror', error => {
  console.error('Page JavaScript error:', error);
});

The pageerror event is separate from Node’s unhandledRejection. Inspect both when navigation appears successful but page behavior is broken. Event payload details can vary by Puppeteer release; verify them against the version in your lockfile and the PageEvents API.

Debugging the underlying cause

Node-side debugging

Use Node’s inspector when the failure is in your script or a Puppeteer call made from Node. Set breakpoints around the call and inspect the rejection stack.

Browser-side debugging

For code executed with page.evaluate() or loaded by the site, enable browser developer tools and use a debugger statement in the page context. Relay console output and listen for pageerror so browser failures are visible in your terminal.

Protocol traffic and pending calls

Puppeteer’s debugging guide documents NODE_DEBUG="puppeteer:*" for protocol logging. If an asynchronous call never resolves, inspect browser.debugInfo.pendingProtocolErrors where supported. These tools diagnose protocol problems; they are not substitutes for handling the promise.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Request interception: the asynchronous handler trap

When request interception is enabled, each request stalls until a handler continues, responds, or aborts it. Puppeteer’s request-interception guide documents returning a promise from an asynchronous handler so Puppeteer can await the handler.

await page.setRequestInterception(true);

page.on('request', async request => {
  try {
    await checkPolicy(request.url());

    // Another listener may have resolved it while we awaited.
    if (request.isInterceptResolutionHandled()) return;
    await request.continue();
  } catch (error) {
    console.error('Interception policy failed:', error);
    if (!request.isInterceptResolutionHandled()) {
      await request.abort();
    }
  }
});

Recheck request.isInterceptResolutionHandled() immediately before continue, respond, or abort. The check and resolution must be synchronous together after the await. This prevents a race in which another listener has already resolved the request. It does not replace catching rejection from your own asynchronous policy function. See the request interception guide.

Common symptoms and fixes

Symptom Likely cause Fix
Process prints UnhandledPromiseRejection after a click or navigation The Puppeteer promise was started but not awaited or returned Await it in the workflow or return the chain and attach .catch()
Top-level catch never runs An event callback, timer, or forEach callback detached its promise Return the callback’s promise where supported, or move work into an awaited loop
Navigation succeeds but the page is broken Exception occurred in page JavaScript Listen for pageerror and relay the page’s console event
“Request is already handled” or interception resolution error Another listener resolved the request during an asynchronous wait Recheck isInterceptResolutionHandled() immediately before resolving
Browser hangs with pending calls A protocol operation is waiting or a handler stalled interception Enable NODE_DEBUG="puppeteer:*", inspect pending protocol errors, and ensure every intercepted request is resolved
Adding a global listener appears to “fix” crashes The listener only observes or changes process behavior Repair the owning promise and keep global events for logging and monitoring

Reliability and error-policy checklist

  • Keep one clear owner for each Puppeteer promise.
  • Await browser creation, page creation, navigation, actions, evaluations, screenshots, PDFs, and shutdown.
  • Use finally for cleanup, with an explicit policy for cleanup errors.
  • Include URL, operation name, and the original stack in logs.
  • Set timeouts deliberately and handle timeout errors at the workflow boundary.
  • Do not continue normal service operation after an uncaught exception merely because a listener logged it; Node warns that resuming after uncaughtException is unsafe.
  • Check your installed Puppeteer version. The examples here align with documentation for Puppeteer 25.12.0 request interception and debugging and 25.11.0 PageEvents; APIs can change.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your goal is simply a clean image or PDF rather than browser automation code, ScreenshotNeo provides a website screenshot API and MCP server. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status.

One GET request is enough:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo API documentation for the 63 capture options, including full-page lazy-image loading, CSS-selector element capture, device presets, dark mode, retina scale, PDF settings, custom CSS and JavaScript, clicks, waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed image links, asynchronous webhooks, bulk capture, usage data, and the OpenAPI specification.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

It also exposes an MCP server with take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots each month without a card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

FAQ

Should I add process.on('unhandledRejection') to every script?

Use it when you need process-level diagnostics or monitoring. It should not replace local await/try…catch handling or be treated as proof that the failed operation recovered.

Why does a rejection appear even though I have a catch?

The catch may be attached to a different promise. A .then() callback, timer, event listener, or array iteration can create a new promise that is never returned to the chain you catch.

Can request interception cause an unhandled rejection?

Yes. The handler’s asynchronous policy work can reject, and a separate race can occur if another listener resolves the request while it is awaiting. Catch the policy error and recheck interception state before resolving.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Which version should I follow?

Match your package lockfile. The documented behavior referenced here is from Node.js v26.10.0 documentation and Puppeteer documentation versions 25.12.0 and 25.11.0; confirm details for newer releases.

Frequently Asked Questions

Is an unhandled rejection the same as an uncaught exception?

No. An unhandled rejection concerns a rejected promise without a timely handler. Node may later raise it as an uncaught exception according to its rejection policy, but the terms describe different stages and signals.

Should I retry every failed Puppeteer call?

No. Retry only errors your application classifies as transient, such as a selected navigation timeout. Authentication failures, invalid selectors, and page-code bugs generally require a fix rather than an automatic retry.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Read next

Recommended PC Tool
Recommended PC Tool
Windows Errors? Fix Them Before They SpreadFree repair scan
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.