Skip to content

Commit 476486d

Browse files
Add throttled ConfigLoader to cache try_examples config
1 parent 02f9937 commit 476486d

File tree

1 file changed

+97
-40
lines changed

1 file changed

+97
-40
lines changed

jupyterlite_sphinx/jupyterlite_sphinx.js

Lines changed: 97 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -149,55 +149,112 @@ var tryExamplesGlobalMinHeight = 0;
149149
*/
150150
var tryExamplesConfigLoaded = false;
151151

152-
window.loadTryExamplesConfig = async (configFilePath) => {
153-
if (tryExamplesConfigLoaded) {
154-
return;
155-
}
156-
try {
157-
// Add a timestamp as query parameter to ensure a cached version of the
158-
// file is not used.
159-
const timestamp = new Date().getTime();
160-
const configFileUrl = `${configFilePath}?cb=${timestamp}`;
161-
const currentPageUrl = window.location.pathname;
162-
163-
const response = await fetch(configFileUrl);
164-
if (!response.ok) {
165-
if (response.status === 404) {
166-
// Try examples ignore file is not present.
167-
console.log("Optional try_examples config file not found.");
168-
return;
169-
}
170-
throw new Error(`Error fetching ${configFilePath}`);
152+
// A config loader with imprved error handling + request deduplication
153+
const ConfigLoader = (() => {
154+
// setting a private state for managing requests and errors
155+
let configLoadPromise = null;
156+
let lastErrorTimestamp = 0;
157+
const ERROR_THROTTLE_MS = 5000; // error messages at most every 5 seconds
158+
const failedRequestsCache = new Set();
159+
160+
const shouldShowError = () => {
161+
const now = Date.now();
162+
if (now - lastErrorTimestamp > ERROR_THROTTLE_MS) {
163+
lastErrorTimestamp = now;
164+
return true;
171165
}
166+
return false;
167+
};
172168

173-
const data = await response.json();
174-
if (!data) {
169+
const logError = (message) => {
170+
if (shouldShowError()) {
171+
console.log(message);
172+
}
173+
};
174+
175+
const loadConfig = async (configFilePath) => {
176+
if (tryExamplesConfigLoaded) {
175177
return;
176178
}
177179

178-
// Set minimum iframe height based on value in config file
179-
if (data.global_min_height) {
180-
tryExamplesGlobalMinHeight = parseInt(data.global_min_height);
180+
if (failedRequestsCache.has(configFilePath)) {
181+
return;
181182
}
182183

183-
// Disable interactive examples if file matches one of the ignore patterns
184-
// by hiding try_examples_buttons.
185-
Patterns = data.ignore_patterns;
186-
for (let pattern of Patterns) {
187-
let regex = new RegExp(pattern);
188-
if (regex.test(currentPageUrl)) {
189-
var buttons = document.getElementsByClassName("try_examples_button");
190-
for (var i = 0; i < buttons.length; i++) {
191-
buttons[i].classList.add("hidden");
184+
// Return the existing promise if the request is in progress, as we
185+
// don't want to make multiple requests for the same file. This
186+
// can happen if there are several try_examples directives on the
187+
// same page.
188+
if (configLoadPromise) {
189+
return configLoadPromise;
190+
}
191+
192+
configLoadPromise = (async () => {
193+
try {
194+
// Add a timestamp as query parameter to ensure a cached version of the
195+
// file is not used.
196+
const timestamp = new Date().getTime();
197+
const configFileUrl = `${configFilePath}?cb=${timestamp}`;
198+
const currentPageUrl = window.location.pathname;
199+
200+
const response = await fetch(configFileUrl);
201+
if (!response.ok) {
202+
if (response.status === 404) {
203+
failedRequestsCache.add(configFilePath);
204+
logError("Optional try_examples config file not found.");
205+
return;
206+
}
207+
throw new Error(`Error fetching ${configFilePath}`);
208+
}
209+
210+
const data = await response.json();
211+
if (!data) {
212+
return;
192213
}
193-
break;
214+
215+
// Set minimum iframe height based on value in config file
216+
if (data.global_min_height) {
217+
tryExamplesGlobalMinHeight = parseInt(data.global_min_height);
218+
}
219+
220+
// Disable interactive examples if file matches one of the ignore patterns
221+
// by hiding try_examples_buttons.
222+
Patterns = data.ignore_patterns;
223+
for (let pattern of Patterns) {
224+
let regex = new RegExp(pattern);
225+
if (regex.test(currentPageUrl)) {
226+
var buttons = document.getElementsByClassName(
227+
"try_examples_button",
228+
);
229+
for (var i = 0; i < buttons.length; i++) {
230+
buttons[i].classList.add("hidden");
231+
}
232+
break;
233+
}
234+
}
235+
} catch (error) {
236+
console.error(error);
237+
} finally {
238+
tryExamplesConfigLoaded = true;
239+
configLoadPromise = null;
194240
}
195-
}
196-
} catch (error) {
197-
console.error(error);
198-
}
199-
tryExamplesConfigLoaded = true;
200-
};
241+
})();
242+
243+
return configLoadPromise;
244+
};
245+
246+
return {
247+
loadConfig,
248+
resetState: () => {
249+
tryExamplesConfigLoaded = false;
250+
configLoadPromise = null;
251+
failedRequestsCache.clear();
252+
lastErrorTimestamp = 0;
253+
},
254+
};
255+
})();
256+
257+
window.loadTryExamplesConfig = ConfigLoader.loadConfig;
201258

202259
window.toggleTryExamplesButtons = () => {
203260
/* Toggle visibility of TryExamples buttons. For use in console for debug

0 commit comments

Comments
 (0)