sampotts/plyr · error · Error

request.status

Error message

request.status

What it means

This is the fetch() XHR wrapper's network-error path. It runs only when XMLHttpRequest fires its 'error' event (network-level failure: CORS rejection, DNS failure, connection refused, offline, mixed-content blocking) — NOT for HTTP 4xx/5xx, which fire 'load'. The handler is defective: it throws synchronously inside an async event callback, so the throw escapes the Promise constructor's try/catch. Consequence: the returned promise is never rejected (it hangs forever) and the throw becomes an uncaught global exception. Additionally, request.status on a network 'error' is a number (commonly 0), so new Error(request.status) yields the unhelpful message '0'.

Solutions

  1. Open DevTools Network tab, find the failing request, and read the real cause (status '(failed)', CORS error, blocked:mixed-content) — then fix the underlying reachability/CORS issue.
  2. Ensure the resource server sends Access-Control-Allow-Origin matching the page origin (and Access-Control-Allow-Credentials if withCredentials is used).
  3. Host the resource same-origin to sidestep CORS entirely.
  4. Race the fetch promise against a timeout so a hanging promise cannot stall the UI (the library's reject never fires).
  5. If you maintain this code, patch the handler to call reject(new Error(...)) with a descriptive message instead of throw.

Example fix

// before (library, buggy)
request.addEventListener('error', () => {
  throw new Error(request.status);
});

// after (patched)
request.addEventListener('error', () => {
  reject(new Error(`Network request failed for ${url} (status: ${request.status})`));
});
Defensive patterns

Strategy: validation

Validate before calling

// Because the library's error handler is buggy (it throws instead of reject,
// leaving the promise pending forever), the robust defense is to pre-check the
// URL and to race the call against a timeout so the UI never stalls.
function safeFetch(url, responseType, withCredentials, timeoutMs = 8000) {
  // Cheap static checks before issuing the request
  try {
    const u = new URL(url, window.location.href);
    if (window.location.protocol === 'https:' && u.protocol === 'http:') {
      return Promise.reject(new Error(`Mixed content blocked: ${url}`));
    }
  } catch {
    return Promise.reject(new Error(`Invalid URL: ${url}`));
  }

  return Promise.race([
    fetch(url, responseType, withCredentials),
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error(`fetch timed out after ${timeoutMs}ms: ${url}`)), timeoutMs)
    )
  ]);
}

Type guard

// Narrow a URL to same-origin, where CORS cannot trigger the 'error' path.
function isSameOrigin(url) {
  try {
    const u = new URL(url, window.location.href);
    return u.origin === window.location.origin;
  } catch {
    return false;
  }
}

// Use a same-origin endpoint for thumbnails/captions whenever possible:
if (!isSameOrigin(thumbnailUrl)) {
  console.warn('Cross-origin resource; ensure CORS headers are present:', thumbnailUrl);
}

Try / catch

// NOTE: the library never rejects on network failure, so .catch() alone will NOT
// fire. Wrap the call in a timeout race (see validationCode) so you actually get
// a rejection to handle. Then:
safeFetch(vttUrl, 'text', false)
  .then(text => parseVtt(text))
  .catch(err => {
    // err.message tells you whether it was timeout, mixed-content, or invalid URL
    console.warn('Thumbnail load failed, continuing without previews:', err.message);
    return null;
  });

Prevention

When it happens

Trigger: Any internal fetch(url, ...) call (VTT thumbnails, captions, etc.) whose request fails at the network layer: the 'error' event fires. Typical causes are cross-origin resources lacking Access-Control-Allow-Origin, offline/no connectivity, DNS resolution failure, connection refused, or mixed http/https content being blocked by the browser.

Common situations: Serving thumbnail VTT or caption files from a CDN/different origin without CORS headers; developing while offline; a CDN outage or typo'd host in the resource URL; HTTP resources referenced from an HTTPS page (mixed content); aggressive ad-blockers/firewalls dropping the sub-resource request.


AI-assisted analysis of sampotts/plyr@6520022413 (2026-08-13). Data as JSON: /api/errors/891c12cb2c88fec0. Report an issue: GitHub.

Appendix: source

Thrown at src/js/utils/fetch.js:34

        request.withCredentials = true;
      }

      request.addEventListener('load', () => {
        if (responseType === 'text') {
          try {
            resolve(JSON.parse(request.responseText));
          }
          catch {
            resolve(request.responseText);
          }
        }
        else {
          resolve(request.response);
        }
      });

      request.addEventListener('error', () => {
        throw new Error(request.status);
      });

      request.open('GET', url, true);
      request.responseType = responseType;
      request.send();
    }
    catch (error) {
      reject(error);
    }
  });
}

View on GitHub (pinned to 6520022413)