test fix for request error handling

This commit is contained in:
Sj-Si 2024-04-18 13:09:58 -04:00
parent 157073b5b2
commit 57d05543df
5 changed files with 107 additions and 88 deletions

View file

@ -10,9 +10,9 @@
// Many operations can be lenghty. Try to limit their frequency by debouncing.
const SCROLL_DEBOUNCE_TIME_MS = 50;
const RESIZE_OBSERVER_DEBOUNCE_TIME_MS = 50; // should be <= refresh debounce time
const RESIZE_OBSERVER_DEBOUNCE_TIME_MS = 100; // should be <= refresh debounce time
const ELEMENT_OBSERVER_DEBOUNCE_TIME_MS = 100;
const REFRESH_DEBOUNCE_TIME_MS = 50;
const REFRESH_DEBOUNCE_TIME_MS = 100;
class Clusterize {
scroll_elem = null;
@ -125,12 +125,12 @@ class Clusterize {
this.setup_has_run = true;
}
clear(loading) {
clear(custom_text) {
if (!this.setup_has_run || !this.enabled) {
return;
}
this.#html(this.#generateEmptyRow(loading).join(""));
this.#html(this.#generateEmptyRow(custom_text).join(""));
}
destroy() {
@ -226,7 +226,11 @@ class Clusterize {
if (!this.enabled) {
return;
}
return await this.options.callbacks.fetchData(idx_start, idx_end);
try {
return await this.options.callbacks.fetchData(idx_start, idx_end);
} catch (error) {
throw error;
}
}
sortDataDefaultCallback() {
@ -335,7 +339,7 @@ class Clusterize {
this.#max_rows = Math.ceil(this.#max_items / this.options.cols_in_block, 10);
return prev_options === JSON.stringify(this.options);
return prev_options !== JSON.stringify(this.options);
}
#getClusterNum() {
@ -346,26 +350,26 @@ class Clusterize {
return Math.min(current_cluster, max_cluster);
}
#generateEmptyRow(loading) {
// If loading==true, then we use the loading text for our element. Defaults to false.
loading = loading === true;
if (!loading && (!this.options.tag || !this.options.show_no_data_row)) {
return [];
#generateEmptyRow(text, class_name) {
if (isNullOrUndefined(text)) {
text = this.options.no_data_text;
}
const text = loading ? this.options.loading_data_text : this.options.no_data_text;
if (isNullOrUndefined(class_name)) {
class_name = this.options.no_data_class;
}
const empty_row = document.createElement(this.options.tag);
const no_data_content = document.createTextNode(text);
empty_row.className = this.options.no_data_class;
const content = document.createTextNode(text);
empty_row.className = class_name;
if (this.options.tag === "tr") {
const td = document.createElement("td");
// fixes #53
td.colSpan = 100;
td.appendChild(no_data_content);
td.appendChild(content);
empty_row.appendChild(td);
} else {
empty_row.appendChild(no_data_content);
empty_row.appendChild(content);
}
return [empty_row.outerHTML];
}
@ -380,7 +384,11 @@ class Clusterize {
const idx_start = Math.max(0, rows_start * this.options.cols_in_block);
const idx_end = Math.min(this.#max_items, rows_end * this.options.cols_in_block);
const this_cluster_rows = await this.fetchData(idx_start, idx_end);
let this_cluster_rows = await this.fetchData(idx_start, idx_end);
if (!Array.isArray(this_cluster_rows) || !this_cluster_rows.length) {
console.error(`Failed to fetch data for idx range (${idx_start},${idx_end})`);
this_cluster_rows = [];
}
if (this_cluster_rows.length < this.options.rows_in_block) {
return {
@ -402,7 +410,14 @@ class Clusterize {
async #insertToDOM() {
if (!this.options.cluster_height || !this.options.cluster_width) {
const rows = await this.fetchData(0, 1);
this.#exploreEnvironment(rows, this.#cache);
if (!Array.isArray(rows) || !rows.length) {
console.error(`Failed to fetch data for idx range (0, 1)`);
this.#html(this.#generateEmptyRow().join(""));
return;
} else {
this.#exploreEnvironment(rows, this.#cache);
this.#html(this.#generateEmptyRow("Loading...").join(""));
}
}
const data = await this.#generate();
@ -502,7 +517,7 @@ class Clusterize {
}
async #onResize() {
await this.refresh(true);
await this.refresh();
}
#fixElementReferences() {

View file

@ -18,7 +18,6 @@
const SEARCH_INPUT_DEBOUNCE_TIME_MS = 250;
const EXTRA_NETWORKS_GET_PAGE_READY_MAX_ATTEMPTS = 10;
const EXTRA_NETWORKS_WAIT_FOR_PAGE_READY_ATTEMPT_DELAY_MS = 1000;
const EXTRA_NETWORKS_WAIT_FOR_PAGE_READY_TIMEOUT_MS = 1000;
const EXTRA_NETWORKS_REQUEST_GET_TIMEOUT_MS = 1000;
const EXTRA_NETWORKS_REFRESH_INTERNAL_DEBOUNCE_TIMEOUT_MS = 200;
@ -66,6 +65,9 @@ const _debounce = (handler, timeout_ms) => {
};
class ExtraNetworksTab {
tabname;
extra_networks_tabname;
tabname_full; // {tabname}_{extra_networks_tabname}
tree_list;
cards_list;
container_elem;
@ -327,10 +329,7 @@ class ExtraNetworksTab {
}
}
async waitForServerPageReady(
max_attempts = EXTRA_NETWORKS_GET_PAGE_READY_MAX_ATTEMPTS,
delay_ms = EXTRA_NETWORKS_WAIT_FOR_PAGE_READY_ATTEMPT_DELAY_MS,
) {
async waitForServerPageReady(max_attempts = EXTRA_NETWORKS_GET_PAGE_READY_MAX_ATTEMPTS) {
/** Waits for a page on the server to be ready.
*
* We need to wait for the page to be ready before we can fetch any data.
@ -339,48 +338,35 @@ class ExtraNetworksTab {
* the server since the data isn't ready. This function allows us to wait for
* the server to tell us that it is ready for data requests.
*
* Resolves when the response from the server is {ready: true}.
* Rejects if we exceed the max number of attempts.
*
* Args:
* max_attempts [int]: The max number of reuqests that will be attempted
* max_attempts [int]: The max number of requests that will be attempted
* before giving up. If set to 0, will attempt forever.
* delay_ms [int]: The time between requests to the server. The server
* responds right away with its state so we need to
* slow down our request times.
*/
const err_prefix = `error waiting for server page (${this.extra_networks_tabname})`;
const err_prefix = `error waiting for server page (${this.tabname_full})`;
return new Promise((resolve, reject) => {
let attempt = 0;
const loop = () => {
setTimeout(async() => {
let response;
try {
response = JSON.parse(
await requestGetPromise(
"./sd_extra_networks/page-is-ready",
{extra_networks_tabname: this.extra_networks_tabname},
EXTRA_NETWORKS_WAIT_FOR_PAGE_READY_TIMEOUT_MS,
)
await requestGetPromise(
"./sd_extra_networks/page-is-ready",
{extra_networks_tabname: this.extra_networks_tabname},
EXTRA_NETWORKS_WAIT_FOR_PAGE_READY_TIMEOUT_MS,
);
if (response.ready === true) {
return resolve();
}
return resolve();
} catch (error) {
// If we get anything other than a timeout error, reject.
// Otherwise, fall through.
if (error !== "Request for ./sd_extra_networks/page-is-ready timed out.") {
return reject(`${err_prefix}: ${error}`);
// Otherwise, fall through to retry request.
if (error.status !== 408) {
return reject(`${err_prefix}: uncaught exception: ${JSON.stringify(error)}`);
}
}
// If we got here, then we got a timeout error.
// Timeout errors are acceptable until the max number of
// attempts has been reached.
if (max_attempts !== 0 && attempt++ >= max_attempts) {
return reject(`${err_prefix}: max attempts exceeded`);
} else {
setTimeout(() => loop(), delay_ms);
// small delay since our request has a timeout.
setTimeout(() => loop(), 100);
}
}, 0);
};
@ -392,7 +378,7 @@ class ExtraNetworksTab {
try {
await this.waitForServerPageReady();
} catch (error) {
console.error(error);
console.error(JSON.stringify(error));
return {};
}
@ -400,9 +386,10 @@ class ExtraNetworksTab {
const payload = {tabname: this.tabname, extra_networks_tabname: this.extra_networks_tabname};
const timeout = EXTRA_NETWORKS_REQUEST_GET_TIMEOUT_MS;
try {
return JSON.parse(await requestGetPromise(url, payload, timeout));
const response = await requestGetPromise(url, payload, timeout);
return response.response;
} catch (error) {
console.error(error);
console.error(JSON.stringify(error));
return {};
}
}
@ -411,7 +398,7 @@ class ExtraNetworksTab {
try {
await this.waitForServerPageReady();
} catch (error) {
console.error(error);
console.error(JSON.stringify(error));
return {};
}
@ -419,9 +406,10 @@ class ExtraNetworksTab {
const payload = {tabname: this.tabname, extra_networks_tabname: this.extra_networks_tabname};
const timeout = EXTRA_NETWORKS_REQUEST_GET_TIMEOUT_MS;
try {
return JSON.parse(await requestGetPromise(url, payload, timeout));
const response = await requestGetPromise(url, payload, timeout);
return response.response;
} catch (error) {
console.error(error);
console.error(JSON.stringify(error));
return {};
}
}
@ -430,7 +418,7 @@ class ExtraNetworksTab {
try {
await this.waitForServerPageReady();
} catch (error) {
console.error(error);
console.error(JSON.stringify(error));
return {};
}
@ -438,9 +426,10 @@ class ExtraNetworksTab {
const payload = {extra_networks_tabname: this.extra_networks_tabname, div_ids: div_ids};
const timeout = EXTRA_NETWORKS_REQUEST_GET_TIMEOUT_MS;
try {
return JSON.parse(await requestGetPromise(url, payload, timeout));
const response = await requestGetPromise(url, payload, timeout);
return response.response;
} catch (error) {
console.error(error);
console.error(JSON.stringify(error));
return {};
}
}
@ -449,7 +438,7 @@ class ExtraNetworksTab {
try {
await this.waitForServerPageReady();
} catch (error) {
console.error(error);
console.error(JSON.stringify(error));
return {};
}
@ -457,9 +446,10 @@ class ExtraNetworksTab {
const payload = {extra_networks_tabname: this.extra_networks_tabname, div_ids: div_ids};
const timeout = EXTRA_NETWORKS_REQUEST_GET_TIMEOUT_MS;
try {
return JSON.parse(await requestGetPromise(url, payload, timeout));
const response = await requestGetPromise(url, payload, timeout);
return response.response;
} catch (error) {
console.error(error);
console.error(JSON.stringify(error));
return {};
}
}
@ -735,7 +725,6 @@ function extraNetworksUnrelatedTabSelected(tabname) {
async function extraNetworksTabSelected(tabname_full, show_prompt, show_neg_prompt) {
/** called from python when user selects an extra networks tab */
await waitForKeyInObject({obj: extra_networks_tabs, k: tabname_full});
for (const [k, v] of Object.entries(extra_networks_tabs)) {
if (k === tabname_full) {

View file

@ -101,7 +101,7 @@ class ExtraNetworksClusterize extends Clusterize {
if (this.lru instanceof LRUCache) {
this.lru.clear();
}
super.clear(true);
super.clear("Loading...");
}
async load(force_init_data) {
@ -182,10 +182,12 @@ class ExtraNetworksClusterize extends Clusterize {
const data = {};
// Fetch any div IDs not in the LRU Cache using our callback.
if (missing_div_ids.length !== 0) {
Object.assign(
data,
await this.options.callbacks.fetchData(missing_div_ids),
);
const fetched_data = await this.options.callbacks.fetchData(missing_div_ids);
if (Object.keys(fetched_data).length !== missing_div_ids.length) {
// expected data. got nothing.
return {};
}
Object.assign(data, fetched_data);
}
// Now load any cached IDs from the LRU Cache
@ -230,7 +232,7 @@ class ExtraNetworksClusterizeTreeList extends ExtraNetworksClusterize {
clear() {
this.selected_div_id = null;
super.clear(true);
super.clear("Loading...");
}
getBoxShadow(depth) {

View file

@ -387,6 +387,15 @@ function requestGet(url, data, handler, errorHandler) {
}
function requestGetPromise(url, data, timeout_ms) {
/**Asynchronous `GET` request that returns a promise.
*
* The result will be of the format {status: int, response: JSON object}.
* Thus, the xhr.responseText that we receive is expected to be a JSON string.
* Acceptable status codes for successful requests are 200 <= status < 300.
*/
if (!isNumber(timeout_ms)) {
timeout_ms = 1000;
}
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const args = Object.entries(data).map(([k, v]) => {
@ -395,18 +404,18 @@ function requestGetPromise(url, data, timeout_ms) {
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
return resolve(xhr.responseText);
return resolve({status: xhr.status, response: JSON.parse(xhr.responseText)});
} else {
return reject({status: xhr.status, response: xhr.responseText});
return reject({status: xhr.status, response: JSON.parse(xhr.responseText)});
}
};
xhr.onerror = () => {
return reject({status: xhr.status, response: xhr.responseText});
return reject({status: xhr.status, response: JSON.parse(xhr.responseText)});
};
xhr.ontimeout = () => {
return reject(`Request for ${url} timed out.`);
return reject({status: 408, response: {detail: `Request timeout: ${url}`}});
};
const payload = JSON.stringify(data);