@@ -149,55 +149,112 @@ var tryExamplesGlobalMinHeight = 0;
149
149
*/
150
150
var tryExamplesConfigLoaded = false ;
151
151
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 ;
171
165
}
166
+ return false ;
167
+ } ;
172
168
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 ) {
175
177
return ;
176
178
}
177
179
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 ;
181
182
}
182
183
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 ;
192
213
}
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 ;
194
240
}
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 ;
201
258
202
259
window . toggleTryExamplesButtons = ( ) => {
203
260
/* Toggle visibility of TryExamples buttons. For use in console for debug
0 commit comments