Retry transient foreground requests before surfacing errors

This commit is contained in:
Jonathan Sykes
2026-10-09 21:53:03 +08:00
parent 933e920701
commit d02796b0eb
10 changed files with 282 additions and 26 deletions

View File

@@ -6,6 +6,7 @@
try { manifest = JSON.parse(doc.getElementById('ytp-assets').textContent); }
catch { return; } // The index must supply this map; never fetch one during boot.
const assets = new Map(), groups = new Map(), hooks = new Map();
let warmScheduled = false;
const css = new Set([...doc.querySelectorAll('link[data-lazy-css]')].map(link => link.dataset.lazyCss));
const selected = new Map();
const url = path => selected.get(path) || (manifest.files[path]?.h ? path + '?v=' + manifest.files[path].h : './' + path.replace(/^\//,''));
@@ -50,14 +51,18 @@
const style = /\.css$/.test(path);
if (style && css.has(path)) return Promise.resolve();
if (!style && !/\.js$/.test(path)) return Promise.resolve(); // workers/imports/fonts are not page scripts
const task = new Promise((resolve, reject) => {
const loadOnce = () => new Promise((resolve, reject) => {
const node = doc.createElement(style ? 'link' : 'script');
const timer = setTimeout(() => { node.remove?.(); reject(new Error('Timed out loading ' + path)); }, 20000);
if (style) { node.rel = 'stylesheet'; node.href = url(path); }
else { node.async = false; node.src = url(path); }
node.onload = resolve;
node.onerror = () => reject(new Error('Unable to load ' + path));
node.onload = () => { clearTimeout(timer); resolve(); };
node.onerror = () => { clearTimeout(timer); node.remove?.(); reject(new Error('Unable to load ' + path)); };
doc.head.append(node);
});
const task = root.AsyncGuard?.retry
? root.AsyncGuard.retry(loadOnce, { attempts: 3 })
: loadOnce();
assets.set(path, task);
task.catch(() => assets.delete(path));
return task;
@@ -100,10 +105,17 @@
else root.navigator.serviceWorker?.getRegistration().then(send).catch(()=>{});
},
warm() {
if (!root.navigator.serviceWorker || root.navigator.onLine === false) return;
root.navigator.serviceWorker.ready.then(reg => {
for (const worker of new Set([reg.active, reg.waiting])) worker?.postMessage({type:'COMPLETE_ASSETS'});
}).catch(()=>{});
if (warmScheduled || !root.navigator.serviceWorker || root.navigator.onLine === false) return;
warmScheduled = true;
const start = () => {
warmScheduled = false;
if(root.navigator.onLine === false)return;
root.navigator.serviceWorker.ready.then(reg => {
for (const worker of new Set([reg.active, reg.waiting])) worker?.postMessage({type:'COMPLETE_ASSETS'});
}).catch(()=>{});
};
if (root.requestIdleCallback) root.requestIdleCallback(start, { timeout: 8000 });
else setTimeout(start, 5000);
},
onLoad(name, fn) {
if(api.loaded(name)) { Promise.resolve().then(fn).catch(error=>{if(root.CustomEvent)root.dispatchEvent?.(new root.CustomEvent('ytp-lazy-error',{detail:{group:name,error}}));}); return; }