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:
- Always add
.catch()
handlers to promises - Implement a global
unhandledrejection
listener - Add custom timeout wrappers for critical operations
- Use retry logic for transient failures
- 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.