v135 · network

Error reporter

Errors thrown late in a session — during page unload, after a crash prompt, or at navigation — are often lost with fetch() because the page may be discarded before the request completes. fetchLater() queues the report durably: it fires when the page is hidden or after a timeout, surviving navigations.

Feature detection: checking…
fetch() — unreliable on unload
fetch('/report', { method: 'POST', body: … }) inside beforeunload or pagehide is not guaranteed to complete. Browsers may discard in-flight requests when navigating away. Keepalive fetch helps but has payload limits and can't be retried.
fetchLater() — deferred and durable
fetchLater('/report', { method: 'POST', body: … }) registers a deferred request that the browser guarantees to send when the page enters the back/forward cache or is discarded. Works across navigations. No retry logic needed.
Error simulator — trigger errors to queue reports ready
Report queue
queued: 0
flushed: 0
would-have-lost: 0
No reports yet — trigger an error above.
Delivery guarantee comparison
fetchLater() delivered
0
fetch() would deliver
0
fetch() on unload lost
0
// Error reporter using fetchLater()
class ErrorReporter {
  constructor(endpoint) {
    this._endpoint = endpoint;
    this._buffer = [];
    this._deferredHandle = null;
  }

  report(level, message, context = {}) {
    const payload = {
      level, message, context,
      url: location.href,
      ts: Date.now(),
      userAgent: navigator.userAgent,
    };
    this._buffer.push(payload);

    // Register/update the deferred request
    this._deferredHandle?.abort();
    this._deferredHandle = fetchLater(this._endpoint, {
      method: 'POST',
      body: JSON.stringify(this._buffer),
      headers: { 'Content-Type': 'application/json' },
      // activateAfter: fires after 30s even if page stays open
      activateAfter: 30_000,
    });
  }
}

// Auto-capture unhandled errors
window.addEventListener('error', e => reporter.report('error', e.message));
window.addEventListener('unhandledrejection', e =>
  reporter.report('warn', String(e.reason)));

see also

references