From 0c1ef561589e46512099f7d5ee915f7a7b724373 Mon Sep 17 00:00:00 2001 From: Panagiotis Astithas Date: Fri, 3 Jan 2025 11:42:07 -0800 Subject: [PATCH] Use one-way binding for loading-state (#4186) Also, make some code health improvements while I'm here: - Mark private methods with an underscore - Use console.error for error messages - Use numerice comparison for numeric variables - Make isLoading read-only - Add comments --- webapp/components/loading-state.js | 64 +++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/webapp/components/loading-state.js b/webapp/components/loading-state.js index 9dad360651..44788bf2cf 100644 --- a/webapp/components/loading-state.js +++ b/webapp/components/loading-state.js @@ -4,46 +4,74 @@ * found in the LICENSE file. */ -/* -LoadingState is a behaviour component for indicating when information is -still being loaded (generally, fetched). -*/ - - +/** + * LoadingState is a behaviour component for indicating when information is + * still being loaded (generally, fetched). + */ const LoadingState = (superClass) => class extends superClass { static get properties() { return { + /** + * The number of active loading operations. + */ loadingCount: { type: Number, value: 0, - observer: 'loadingCountChanged', + observer: '_loadingCountChanged', }, + /** + * Whether the component is currently loading data. + * Computed based on `loadingCount`. + */ isLoading: { type: Boolean, - computed: 'computeIsLoading(loadingCount)', - notify: true, + value: false, + computed: '_computeIsLoading(loadingCount)', + readOnly: true, + }, + /** + * A callback function to be executed when loading is complete. + */ + onLoadingComplete: { + type: Function, }, - onLoadingComplete: Function, }; } - computeIsLoading(loadingCount) { - return !!loadingCount; + /** + * Computes the `isLoading` property based on `loadingCount`. + * @param {number} loadingCount The current loading count. + * @return {boolean} True if loading, false otherwise. + */ + _computeIsLoading(loadingCount) { + return loadingCount > 0; } - loadingCountChanged(now, then) { + /** + * Observer for `loadingCount` changes. + * Calls `onLoadingComplete` when loading finishes. + * @param {number} now The new loading count. + * @param {number} then The previous loading count. + */ + _loadingCountChanged(now, then) { if (now === 0 && then > 0 && this.onLoadingComplete) { this.onLoadingComplete(); } } + /** + * Tracks a promise, incrementing `loadingCount` while it's pending. + * @param {Promise} promise The promise to track. + * @param {Function} opt_errHandler An optional error handler. + * @return {Promise} A promise that resolves/rejects with the original promise. + */ async load(promise, opt_errHandler) { this.loadingCount++; try { return await promise; } catch (e) { // eslint-disable-next-line no-console - console.log(`Failed to load: ${e}`); + console.error(`Failed to load: ${e}`); if (opt_errHandler) { opt_errHandler(e); } @@ -52,6 +80,14 @@ const LoadingState = (superClass) => class extends superClass { } } + /** + * Retries a function with exponential backoff. + * @param {Function} f The function to retry. + * @param {Function} shouldRetry A function that determines if retrying should continue. + * @param {number} num The maximum number of retries. + * @param {number} wait The initial wait time in milliseconds. + * @return {Promise} A promise that resolves with the result of `f` or rejects. + */ retry(f, shouldRetry, num, wait) { let count = 0; const retry = () => {