Unhandled Promise rejection: Timeout is a common error from third-party libraries that set time limits on their operations. When Google reCAPTCHA, Analytics, Cloudflare Turnstile, or network libraries like Axios don’t get responses in time, they reject their promises with a “Timeout” error.

Unlike traditional JavaScript errors, Promise rejections don’t trigger window.onerror. They float up silently until they hit the unhandledrejection event handler. If you’re not listening for them, you’ll never know your users are experiencing timeouts.

The Problem

Third-party services reject promises with “Timeout” when they don’t complete within their time limits:

// Google reCAPTCHA waiting for verification
grecaptcha.execute('site_key', {action: 'submit'})
  .then(token => {
    // Never gets here if it times out
  })
  // Without this catch, you get an unhandled rejection

The promise silently rejects with “Timeout” after the library’s internal timer expires. Without a .catch() handler, this becomes an unhandled rejection that can break functionality without any visible error.

Understanding the Root Cause

Promise timeouts happen when an async operation takes too long. Libraries implement timeouts to prevent hanging indefinitely on slow or failed network requests.

Google reCAPTCHA Timeouts

reCAPTCHA sets internal timeouts for verification requests. When the Google servers are slow or blocked:

// Simplified version of what happens inside reCAPTCHA
new Promise(function(resolve, reject) {
    // Make verification request
    makeRequest(function(response) {
        resolve(response);
    });

    // Timeout after N seconds
    setTimeout(function() {
        reject("Timeout");
    }, timeoutDuration);
})

Google Analytics Timeouts

Google Analytics (gtag.js) has timeouts for sending events. If the analytics servers don’t respond quickly enough, the promise rejects.

Cloudflare Turnstile Timeouts

Cloudflare’s Turnstile CAPTCHA alternative times out when verification takes too long, similar to reCAPTCHA.

Axios Request Timeouts

Axios allows you to set explicit timeouts:

axios.get('/api/data', {
    timeout: 5000 // 5 seconds
})
// Rejects with "Timeout"

Network and Environmental Causes

  • Slow networks: Mobile users, rural connections, congested WiFi
  • Blocked requests: Corporate firewalls, country-level blocks, ad blockers
  • Server issues: Backend overload, DDoS attacks, maintenance
  • Browser throttling: Background tabs, low battery mode, CPU constraints

How to Fix Timeout Promise Rejections

The key is to always handle promise rejections and provide fallback behavior for timeouts.

Troubleshooting Checklist

  • Check browser DevTools Network tab for slow/failed requests
  • Verify third-party services are accessible from user’s location
  • Test on slow 3G throttling to simulate poor connections
  • Check if ad blockers or privacy extensions are blocking requests
  • Confirm backend services are responding within expected times

Step 1: Always Add Catch Handlers

Never let promises go unhandled:

// BAD: Unhandled rejection
grecaptcha.execute('site_key', {action: 'submit'})
  .then(token => {
    submitForm(token);
  });

// GOOD: Handled timeout
grecaptcha.execute('site_key', {action: 'submit'})
  .then(token => {
    submitForm(token);
  })
  .catch(error => {
    if (error === 'Timeout') {
      console.warn('reCAPTCHA timed out, proceeding without it');
      submitFormWithoutCaptcha();
    } else {
      console.error('reCAPTCHA error:', error);
    }
  });

Step 2: Implement Global Rejection Handler

Catch all unhandled rejections as a safety net:

window.addEventListener('unhandledrejection', event => {
  const error = event.reason;

  // Log timeout errors differently
  if (error === 'Timeout' ||
      (typeof error === 'string' && error.includes('timeout'))) {
    console.warn('Timeout detected:', error);

    // Track which service timed out if possible
    if (event.promise && event.promise.toString().includes('recaptcha')) {
      trackEvent('recaptcha_timeout');
    }

    // Prevent the default browser error
    event.preventDefault();
  }
});

Step 3: Add Your Own Timeout Wrapper

For critical operations, implement your own timeout logic:

function withTimeout(promise, timeoutMs, timeoutMessage) {
  return Promise.race([
    promise,
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error(timeoutMessage)), timeoutMs)
    )
  ]);
}

// Use it for critical third-party calls
withTimeout(
  grecaptcha.execute('site_key', {action: 'submit'}),
  10000,  // 10 seconds
  'reCAPTCHA took too long'
)
.then(token => {
  submitForm(token);
})
.catch(error => {
  console.error('CAPTCHA failed:', error.message);
  // Decide whether to proceed without CAPTCHA
});

Step 4: Implement Retry Logic

For transient network issues, retry with backoff:

async function executeWithRetry(fn, maxRetries = 3) {
  let lastError;

  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error;

      // Only retry on timeout
      if (error !== 'Timeout' && !error.message?.includes('timeout')) {
        throw error;
      }

      // Wait before retrying (exponential backoff)
      if (i < maxRetries - 1) {
        const delay = Math.min(1000 * Math.pow(2, i), 10000);
        console.log(`Timeout, retrying in ${delay}ms...`);
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
  }

  throw lastError;
}

// Usage
executeWithRetry(() => grecaptcha.execute('site_key', {action: 'submit'}))
  .then(token => submitForm(token))
  .catch(error => handleFinalFailure(error));

Step 5: Graceful Degradation

Design your app to work even when third-party services timeout:

class FormSubmitter {
  async submit(formData) {
    let captchaToken = null;

    // Try to get CAPTCHA but don't block submission
    try {
      captchaToken = await Promise.race([
        grecaptcha.execute('site_key', {action: 'submit'}),
        new Promise((_, reject) =>
          setTimeout(() => reject('Timeout'), 5000)
        )
      ]);
    } catch (error) {
      console.warn('CAPTCHA failed, submitting without it:', error);
      // Track this for monitoring
      trackEvent('form_submitted_without_captcha');
    }

    // Submit with or without CAPTCHA
    return await this.sendToServer({
      ...formData,
      captcha: captchaToken || 'timeout'
    });
  }
}

Step 6: Monitor Network Errors in Production

This is not the sort of error you will find during testing. It only pops up in production when one of your APIs goes down. To know when that happens, you’ll need to set up frontend monitoring.

When to Ignore This Error

You might choose to suppress timeout errors when:

  • Non-critical third-party services: Analytics or tracking that doesn’t affect core functionality
  • Known slow networks: Users in regions with poor connectivity where timeouts are expected
  • Graceful degradation works: Your fallback behavior handles the timeout appropriately
  • During load testing: When you’re intentionally overwhelming services

Summary

“Timeout” Promise rejections occur when async operations exceed their time limits. They’re especially common with third-party services like Google reCAPTCHA, Analytics, and Cloudflare Turnstile. To handle them properly:

  1. Always add .catch() handlers to promises
  2. Implement a global unhandledrejection listener
  3. Add custom timeout wrappers for critical operations
  4. Use retry logic for transient failures
  5. Design for graceful degradation

Most importantly, never assume third-party services will respond quickly or at all. The internet is unreliable, and timeouts are inevitable. Plan for them.

TrackJS is the easy way to monitor your JavaScript applications and fix production errors. TrackJS is provides detailed error monitoring and alerting to developers around the world at companies like 3M, Tidal, IKEA, Venmo, Allbirds, and Frontend Masters. TrackJS catches millions of errors from users everyday. Let's start catching yours.

Protect your JavaScript