-
-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Use sequential loading and requests for all UI resources #5013
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
- load common.js and style.css sequentially for all config pages - restrict all requrests in index.js to single connection
WalkthroughThis PR refactors WLED's initialization system to use dynamic resource loading with retry logic. It converts callback-based data loading in index.js to async/await patterns, adds a centralized loadResources function in common.js, and updates all settings HTML pages to dynamically load dependencies rather than using static includes. Additionally, a memory pre-check is removed from ws.cpp. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Rationale: The refactor spans significant scope with dense async/await logic changes in index.js affecting multiple function signatures and initialization flow. While the 10+ HTML files follow a consistent, repetitive pattern (reducing per-file cognitive load), the aggregate number of files and need to verify consistency across all pages increases overall review effort. The backend change is minor but requires context verification. Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (6)
wled00/data/common.js (1)
54-82
: Bound retries + backoff; avoid perma-hidden UI and tight retry loopCurrent logic retries indefinitely every 100ms on load error, which can keep the UI hidden forever if a resource is missing or path is wrong. Add a bounded retry with backoff and a safe skip to the next resource, plus a once-only load handler and fix a small comment typo.
Apply this diff:
-function loadResources(files, init) { - let i = 0; +function loadResources(files, init, opts) { + let i = 0; + const retries = opts?.retries ?? 5; + const baseWait = opts?.delay ?? 100; // ms + const backoff = opts?.backoff ?? 1.5; // multiplier + const attempts = Object.create(null); const loadNext = () => { if (i >= files.length) { if (init) { - d.documentElement.style.visibility = 'visible'; // make page visible after all files are laoded if it was hidden (prevent ugly display) - d.readyState === 'complete' ? init() : window.addEventListener('load', init); + d.documentElement.style.visibility = 'visible'; // make page visible after all files are loaded if it was hidden (prevent ugly display) + if (d.readyState === 'complete') init(); + else window.addEventListener('load', init, { once: true }); } return; } const file = files[i++]; const isCSS = file.endsWith('.css'); const el = d.createElement(isCSS ? 'link' : 'script'); if (isCSS) { el.rel = 'stylesheet'; el.href = file; } else { el.src = file; } el.onload = () => { setTimeout(loadNext, 0);}; - el.onerror = () => { - i--; // load this file again - setTimeout(loadNext, 100); - }; + el.onerror = () => { + const n = (attempts[file] || 0) + 1; + attempts[file] = n; + if (n >= retries) { + console.warn(`loadResources: giving up on ${file} after ${n} attempts`); + // skip this file and continue with the rest + setTimeout(loadNext, 0); + return; + } + // retry same file with capped exponential backoff + const wait = Math.min(5000, Math.round(baseWait * Math.pow(backoff, n - 1))); + i--; // reattempt current file + setTimeout(loadNext, wait); + }; d.head.appendChild(el); }; loadNext(); }Optionally, let callers pass
{retries, delay, backoff}
to tune behavior per page.wled00/data/settings_ui.htm (1)
11-18
: Add bounded retries and minimal fallback for common.js loaderPrevent an infinite retry loop and blank page if common.js can’t be fetched. Add a small counter, backoff, and a user-visible fallback.
- (function loadFiles() { + (function loadFiles() { + let tries = 0; const l = document.createElement('script'); l.src = 'common.js'; - l.onload = () => loadResources(['style.css'], S); // load style.css then call S() - l.onerror = () => setTimeout(loadFiles, 100); + l.onload = () => loadResources(['style.css'], S); // load style.css then call S() + l.onerror = () => { + if (++tries >= 5) { + console.warn('Failed to load common.js after 5 attempts'); + d.documentElement.style.visibility = 'visible'; + alert('Failed to load common.js. Please reload the page.'); + return; + } + setTimeout(loadFiles, Math.min(5000, 100 * tries)); // simple backoff + }; document.head.appendChild(l); })();wled00/data/settings_time.htm (1)
11-18
: Harden common.js retry loopSame as other settings pages: add bounded retries with backoff and a visible fallback to avoid an endlessly hidden page if common.js fails.
- (function loadFiles() { + (function loadFiles() { + let tries = 0; const l = document.createElement('script'); l.src = 'common.js'; l.onload = () => loadResources(['style.css'], S); // load style.css then call S() - l.onerror = () => setTimeout(loadFiles, 100); + l.onerror = () => { + if (++tries >= 5) { + console.warn('Failed to load common.js after 5 attempts'); + d.documentElement.style.visibility = 'visible'; + alert('Failed to load common.js. Please reload the page.'); + return; + } + setTimeout(loadFiles, Math.min(5000, 100 * tries)); + }; document.head.appendChild(l); })();wled00/data/settings_um.htm (1)
13-21
: Bound retries with backoff for loader; add UX on failureMirror the improved loader pattern so this page doesn’t get stuck hidden if common.js can’t be fetched.
- (function loadFiles() { + (function loadFiles() { + let tries = 0; const l = document.createElement('script'); l.src = 'common.js'; l.onload = () => loadResources(['style.css'], S); // load style.css then call S() - l.onerror = () => setTimeout(loadFiles, 100); + l.onerror = () => { + if (++tries >= 5) { + console.warn('Failed to load common.js after 5 attempts'); + d.documentElement.style.visibility = 'visible'; + alert('Failed to load common.js. Please reload the page.'); + return; + } + setTimeout(loadFiles, Math.min(5000, 100 * tries)); + }; document.head.appendChild(l); })();wled00/data/settings_sec.htm (1)
9-16
: Avoid infinite retry loop; add backoff and user feedbackHarden the loader to prevent an endlessly hidden UI if common.js fails.
- (function loadFiles() { + (function loadFiles() { + let tries = 0; const l = document.createElement('script'); l.src = 'common.js'; l.onload = () => loadResources(['style.css'], S); // load style.css then call S() - l.onerror = () => setTimeout(loadFiles, 100); + l.onerror = () => { + if (++tries >= 5) { + console.warn('Failed to load common.js after 5 attempts'); + d.documentElement.style.visibility = 'visible'; + alert('Failed to load common.js. Please reload the page.'); + return; + } + setTimeout(loadFiles, Math.min(5000, 100 * tries)); + }; document.head.appendChild(l); })();wled00/data/settings_sync.htm (1)
48-50
: RedundantgetURL
function definition.The
getURL
function is defined locally on lines 48-50, but it's already provided bycommon.js
which is loaded dynamically beforeS()
is called (line 13). SincegetURL
is only called from withinS()
(lines 45-46) aftercommon.js
has loaded, this local definition appears redundant.Consider removing the local definition to avoid duplication:
-function getURL(path) { - return (loc ? locproto + "//" + locip : "") + path; -}Note: Verify that no other code paths call
getURL
beforecommon.js
loads.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
wled00/data/common.js
(1 hunks)wled00/data/index.js
(13 hunks)wled00/data/settings.htm
(1 hunks)wled00/data/settings_2D.htm
(2 hunks)wled00/data/settings_dmx.htm
(2 hunks)wled00/data/settings_leds.htm
(3 hunks)wled00/data/settings_sec.htm
(2 hunks)wled00/data/settings_sync.htm
(2 hunks)wled00/data/settings_time.htm
(2 hunks)wled00/data/settings_ui.htm
(2 hunks)wled00/data/settings_um.htm
(2 hunks)wled00/data/settings_wifi.htm
(2 hunks)wled00/ws.cpp
(0 hunks)
💤 Files with no reviewable changes (1)
- wled00/ws.cpp
🧰 Additional context used
📓 Path-based instructions (3)
wled00/data/**/*.{htm,html,css,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use tabs for indentation in web files (.htm/.html/.css/.js) under wled00/data
Files:
wled00/data/common.js
wled00/data/settings_dmx.htm
wled00/data/settings_time.htm
wled00/data/settings_um.htm
wled00/data/settings_leds.htm
wled00/data/settings_sec.htm
wled00/data/settings_wifi.htm
wled00/data/settings_sync.htm
wled00/data/settings_2D.htm
wled00/data/settings_ui.htm
wled00/data/index.js
wled00/data/settings.htm
wled00/data/**
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
wled00/data/**
: When modifying web UI files, runnpm run build
to regenerate embedded headers before any firmware build
For web UI changes, edit files only under wled00/data (not firmware or generated files)
Files:
wled00/data/common.js
wled00/data/settings_dmx.htm
wled00/data/settings_time.htm
wled00/data/settings_um.htm
wled00/data/settings_leds.htm
wled00/data/settings_sec.htm
wled00/data/settings_wifi.htm
wled00/data/settings_sync.htm
wled00/data/settings_2D.htm
wled00/data/settings_ui.htm
wled00/data/index.js
wled00/data/settings.htm
wled00/data/settings*.htm
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Name settings pages as settings*.htm within the web UI
Files:
wled00/data/settings_dmx.htm
wled00/data/settings_time.htm
wled00/data/settings_um.htm
wled00/data/settings_leds.htm
wled00/data/settings_sec.htm
wled00/data/settings_wifi.htm
wled00/data/settings_sync.htm
wled00/data/settings_2D.htm
wled00/data/settings_ui.htm
wled00/data/settings.htm
🧠 Learnings (1)
📚 Learning: 2025-10-05T15:24:05.545Z
Learnt from: CR
PR: wled/WLED#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-05T15:24:05.545Z
Learning: Applies to wled00/data/settings*.htm : Name settings pages as settings*.htm within the web UI
Applied to files:
wled00/data/settings.htm
🪛 Biome (2.1.2)
wled00/data/index.js
[error] 2815-2816: Comparing to itself is potentially pointless.
(lint/suspicious/noSelfCompare)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (15)
- GitHub Check: wled_build / Build Environments (esp32s3_4M_qspi)
- GitHub Check: wled_build / Build Environments (usermods)
- GitHub Check: wled_build / Build Environments (esp32_wrover)
- GitHub Check: wled_build / Build Environments (esp32s3dev_16MB_opi)
- GitHub Check: wled_build / Build Environments (nodemcuv2)
- GitHub Check: wled_build / Build Environments (esp32c3dev)
- GitHub Check: wled_build / Build Environments (esp32_eth)
- GitHub Check: wled_build / Build Environments (esp32s3dev_8MB_opi)
- GitHub Check: wled_build / Build Environments (esp32dev)
- GitHub Check: wled_build / Build Environments (lolin_s2_mini)
- GitHub Check: wled_build / Build Environments (esp8266_2m_160)
- GitHub Check: wled_build / Build Environments (esp01_1m_full_160)
- GitHub Check: wled_build / Build Environments (esp01_1m_full_compat)
- GitHub Check: wled_build / Build Environments (esp8266_2m)
- GitHub Check: wled_build / Build Environments (esp01_1m_full)
🔇 Additional comments (13)
wled00/data/settings_ui.htm (3)
7-7
: LGTM: prevent FOUC while dependencies loadHiding html until resources load avoids flicker; loadResources() makes it visible on completion.
221-221
: LGTM: onload removal aligns with deferred initInit via loader keeps ordering guarantees and avoids “xy is undefined” before common.js.
7-7
: File already uses correct tab-based indentation per guidelinesThe indentation in
wled00/data/settings_ui.htm
is already compliant with coding guidelines—all indented lines use tabs (^I
), not spaces. Ensurenpm run build
is run after making any web UI modifications to regenerate embedded headers before firmware compilation.wled00/data/settings_time.htm (2)
7-7
: LGTM: no-FUOC guard matches loadResources revealHiding html until resource load completes is appropriate here.
132-132
: LGTM: init moved off body onloadConsistent with the new sequential loader flow.
wled00/data/settings_um.htm (2)
7-7
: LGTM: initial hide to prevent unstyled flashPairs correctly with loadResources’ reveal.
284-284
: LGTM: body onload removal aligns with deferred initKeeps init ordering consistent.
wled00/data/settings_sec.htm (2)
7-7
: LGTM: FOUC prevention matches common.js revealConsistent with the shared loader behavior.
42-42
: LGTM: loader-based init replaces body onloadKeeps dependency order deterministic.
wled00/data/settings_dmx.htm (1)
10-16
: Verify infinite retry behavior is intentional.The
loadFiles
IIFE will retry loadingcommon.js
indefinitely every 100ms on failure, whereas other data loading functions inindex.js
limit retries to 5 attempts. Under persistent network failures or misconfigurations, this could result in an infinite loop of retry attempts.Consider whether a retry limit (matching the 5-retry pattern used elsewhere) would be more appropriate:
-(function loadFiles() { +(function loadFiles(attempt = 0) { const l = document.createElement('script'); l.src = 'common.js'; l.onload = () => loadResources(['style.css'], S); // load style.css then call S() - l.onerror = () => setTimeout(loadFiles, 100); + l.onerror = () => { if (attempt < 5) setTimeout(() => loadFiles(attempt + 1), 100); }; document.head.appendChild(l); })();This pattern appears in all settings_*.htm files.
wled00/data/index.js (3)
279-294
: Sequential initialization looks good, but verify error handling doesn't halt progression.The sequential loading pattern correctly addresses the low-heap/503-error issue mentioned in the PR objectives. Each async function properly resolves even on error, allowing initialization to continue.
One minor suggestion: Consider logging initialization progress for debugging:
(async ()=>{ try { + console.log('[WLED] Starting initialization...'); await loadPalettes(); // loads base palettes and builds #pallist (safe first) await loadFXData(); // loads fx data await loadFX(); // populates effect list await loadPalettesData(); // fills palettesData[] for previews await requestJson(); // fills lastinfo.cpalcount (safe now, pallist exists) populatePalettes(); // repopulate with custom palettes now that cpalcount is known await loadPresets(); // load presets last if (cfg.comp.css) await loadSkinCSS('skinCss'); + console.log('[WLED] Initialization complete'); if (!ws) makeWS(); } catch(e) { showToast("Init failed: " + e, true); + console.error('[WLED] Initialization error:', e); } })();
516-537
: Well-structured Promise conversion with retry logic.The conversion from callback-based to Promise-based loading is clean and maintains the retry logic (5 attempts with 100ms intervals). The recursive retry pattern correctly preserves the Promise chain.
1693-1768
: requestJson Promise conversion maintains backward compatibility well.The Promise-based refactoring preserves the WebSocket fallback logic and correctly handles the command parameter. The retry logic (up to 10 attempts for requestJson vs 5 for data loading) seems intentional for higher reliability of state changes.
Minor observation: The higher retry count (10) for
requestJson
compared to data loading (5) appears deliberate for ensuring command reliability.
function loadPalettesData() { | ||
return new Promise((resolve) => { | ||
if (palettesData) return resolve(); // already loaded | ||
const lsKey = "wledPalx"; | ||
var lsPalData = localStorage.getItem(lsKey); | ||
if (lsPalData) { | ||
try { | ||
var d = JSON.parse(lsPalData); | ||
if (d && d.vid == d.vid) { | ||
palettesData = d.p; | ||
return resolve(); | ||
} | ||
} catch (e) {} | ||
} | ||
|
||
palettesData = {}; | ||
getPalettesData(0, ()=>{ | ||
localStorage.setItem(lsKey, JSON.stringify({ | ||
p: palettesData, | ||
vid: lastinfo.vid | ||
})); | ||
redrawPalPrev(); | ||
if (callback) setTimeout(callback, 99); | ||
palettesData = {}; | ||
getPalettesData(0, () => { | ||
localStorage.setItem(lsKey, JSON.stringify({ | ||
p: palettesData, | ||
vid: lastinfo.vid | ||
})); | ||
redrawPalPrev(); | ||
setTimeout(resolve, 99); // delay optional | ||
}); | ||
}); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical bug: Self-comparison in version check.
Line 2815 contains a self-comparison d.vid == d.vid
which will always be true, making the version check ineffective. This appears to be a typo that should compare against lastinfo.vid
.
Apply this fix:
try {
var d = JSON.parse(lsPalData);
- if (d && d.vid == d.vid) {
+ if (d && d.vid == lastinfo.vid) {
palettesData = d.p;
return resolve();
}
} catch (e) {}
Based on static analysis hints.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
function loadPalettesData() { | |
return new Promise((resolve) => { | |
if (palettesData) return resolve(); // already loaded | |
const lsKey = "wledPalx"; | |
var lsPalData = localStorage.getItem(lsKey); | |
if (lsPalData) { | |
try { | |
var d = JSON.parse(lsPalData); | |
if (d && d.vid == d.vid) { | |
palettesData = d.p; | |
return resolve(); | |
} | |
} catch (e) {} | |
} | |
palettesData = {}; | |
getPalettesData(0, ()=>{ | |
localStorage.setItem(lsKey, JSON.stringify({ | |
p: palettesData, | |
vid: lastinfo.vid | |
})); | |
redrawPalPrev(); | |
if (callback) setTimeout(callback, 99); | |
palettesData = {}; | |
getPalettesData(0, () => { | |
localStorage.setItem(lsKey, JSON.stringify({ | |
p: palettesData, | |
vid: lastinfo.vid | |
})); | |
redrawPalPrev(); | |
setTimeout(resolve, 99); // delay optional | |
}); | |
}); | |
} | |
} | |
function loadPalettesData() { | |
return new Promise((resolve) => { | |
if (palettesData) return resolve(); // already loaded | |
const lsKey = "wledPalx"; | |
var lsPalData = localStorage.getItem(lsKey); | |
if (lsPalData) { | |
try { | |
var d = JSON.parse(lsPalData); | |
if (d && d.vid == lastinfo.vid) { | |
palettesData = d.p; | |
return resolve(); | |
} | |
} catch (e) {} | |
} | |
palettesData = {}; | |
getPalettesData(0, () => { | |
localStorage.setItem(lsKey, JSON.stringify({ | |
p: palettesData, | |
vid: lastinfo.vid | |
})); | |
redrawPalPrev(); | |
setTimeout(resolve, 99); // delay optional | |
}); | |
}); | |
} |
🧰 Tools
🪛 Biome (2.1.2)
[error] 2815-2816: Comparing to itself is potentially pointless.
(lint/suspicious/noSelfCompare)
🤖 Prompt for AI Agents
In wled00/data/index.js around lines 2807 to 2832, the version check currently
does a self-comparison (d.vid == d.vid) which is wrong; change that comparison
to validate against the current app version (e.g., d.vid === lastinfo.vid) and
guard for missing lastinfo or lastinfo.vid before comparing; update the if
condition accordingly so that cached palettes are only used when d exists and
d.vid equals lastinfo.vid.
function getPalettesData(page, callback) { | ||
fetch(getURL(`/json/palx?page=${page}`), {method: 'get'}) | ||
.then(res => res.ok ? res.json() : Promise.reject()) | ||
.then(json => { | ||
retry = false; | ||
retry = 0; | ||
palettesData = Object.assign({}, palettesData, json.p); | ||
if (page < json.m) setTimeout(()=>{ getPalettesData(page + 1, callback); }, 75); | ||
else callback(); | ||
}) | ||
.catch((error)=>{ | ||
if (!retry) { | ||
retry = true; | ||
setTimeout(()=>{getPalettesData(page,callback);}, 500); // retry | ||
if (retry<5) { | ||
retry++; | ||
setTimeout(()=>{getPalettesData(page,callback);}, 100); | ||
} else { | ||
showToast(error, true); | ||
callback(); | ||
} | ||
showToast(error, true); | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Verify retry counter reset between page loads.
The retry
variable (line 19) is used across multiple loading functions. Ensure it's properly scoped or reset between different loading operations to prevent retry exhaustion from one function affecting another.
Run the following check to verify retry counter usage:
The retry counter should be reset (e.g., retry = 0
) at the start of each independent loading operation or scoped locally to each function.
🏁 Script executed:
#!/bin/bash
# Check all usages of the retry variable
rg -n '\bretry\b' wled00/data/index.js
Length of output: 378
I need to identify the functions using the retry
variable to understand if they could run concurrently and if there's actual isolation or interference.
Now I need to identify the functions using the retry counter to understand if they can run concurrently.
Let me search differently to identify these functions more clearly:
Let me use a more targeted approach:
Actually, let me search for the specific functions more directly by examining the context around the retry lines:
Let me try a simpler approach - just look at the actual file around the retry lines:
🏁 Script executed:
#!/bin/bash
# Find function definitions containing retry usage
# Search for function declarations near retry lines
rg -B 10 '\bretry\b' wled00/data/index.js | grep -E '(^[0-9]+-.*function |^[0-9]+-.*\bretry\b)'
Length of output: 0
🏁 Script executed:
#!/bin/bash
# Get context for each retry usage - find function names
cat wled00/data/index.js | awk '/^function|^var.*retry|^\t*(if|retry).*retry/ {
if (/^function/) func=$0;
if (/retry/) print NR": " func " >> " $0
}'
Length of output: 97
🏁 Script executed:
#!/bin/bash
# Find lines with retry and show what function they belong to
# Using sed to extract function scope
rg -B 20 '\bretry\b' wled00/data/index.js | grep -A 5 'function.*' | head -100
Length of output: 908
🏁 Script executed:
#!/bin/bash
# Extract the function definitions that contain retry usage
ast-grep --pattern 'function $NAME($_) {
$$$
retry
$$$
}'
Length of output: 0
🏁 Script executed:
#!/bin/bash
# Get context around each retry usage to identify functions
rg -B 25 'retry\s*=' wled00/data/index.js | grep -E '(function |retry\s*=)'
Length of output: 214
🏁 Script executed:
#!/bin/bash
# Show file content around critical retry lines
sed -n '515,560p' wled00/data/index.js
sed -n '2830,2850p' wled00/data/index.js
Length of output: 1466
Now let me check where these functions are called to understand if they execute concurrently:
---
🏁 Script executed:
```shell
#!/bin/bash
# Find where loadPalettes, loadFX, loadFXData, getPalettesData are called
rg -n '(loadPalettes|loadFX|loadFXData|getPalettesData)\(' wled00/data/index.js | grep -v 'function \|async function'
Length of output: 566
Address the global retry variable causing race conditions between concurrent loading functions.
The concern is valid. The code shares a single global retry
variable across multiple async loading functions (loadPalettes
, loadFX
, loadFXData
, getPalettesData
). While the primary execution path (lines 282-284) awaits these sequentially, the retry paths scheduled via setTimeout
can fire concurrently:
-
Race scenario: If
loadPalettes()
fails and schedules a retry viasetTimeout
, thengetPalettesData()
is called and fails simultaneously, both functions increment/reset the same globalretry
counter. The first function to complete its.then()
resetsretry=0
, corrupting the second function's retry tracking. -
Pagination conflict: During
getPalettesData()
pagination with retries active, another loading function's failure can increment the sharedretry
variable, causing one function's exhausted retries to block another's recovery attempts.
Fix: Make retry
local to each function or maintain per-function retry tracking. For example, change from global var retry = 0;
to passing retry
as a parameter or storing it in a scoped context within each async function.
🤖 Prompt for AI Agents
In wled00/data/index.js around lines 2834 to 2852, the function getPalettesData
relies on a shared global retry counter which causes race conditions when
multiple loaders retry concurrently; make retry local to each call by removing
dependency on the global variable and either (A) add a retry parameter to
getPalettesData(page, callback, retry = 0) and use that in recursive calls and
setTimeout retries so each call tracks its own attempts, or (B) declare a local
let retry = 0 inside a wrapper function that defines the recursive inner loader
(or use a closure) so pagination recursion and scheduled retries increment/reset
only that call’s counter; update all call sites accordingly so no global retry
variable is used.
This greatly improves UI stability in low heap as well as bad wifi signal conditions and does not noticeably slow down any UI access.
I also increased the retries on failed requests from 1 to 5 but shortened the request interval so some requests may even be faster than before.
Combined with #4939 this makes the ESP8266 work flawlessly, I was even able to increase the number of LEDs from current 720 to 900 and its still more stable than it is currently. With this PR the UI keeps running down to about 6k of free heap whereas it is already struggeling at 10k without these changes.
the changes add ~900bytes to flash usage.
Summary by CodeRabbit
New Features
Bug Fixes
Chores