mirror of
https://github.com/AUTOMATIC1111/stable-diffusion-webui.git
synced 2026-02-03 14:21:38 -08:00
add timeouts to wait functions. fix control duplication bug.
This commit is contained in:
parent
226267e82f
commit
5d64690565
3 changed files with 181 additions and 95 deletions
|
|
@ -17,6 +17,8 @@
|
|||
/*eslint no-undef: "error"*/
|
||||
|
||||
const SEARCH_INPUT_DEBOUNCE_TIME_MS = 250;
|
||||
const EXTRA_NETWORKS_GET_PAGE_READY_MAX_ATTEMPTS = 10;
|
||||
const EXTRA_NETWORKS_REQUEST_GET_TIMEOUT_MS = 1000;
|
||||
|
||||
const re_extranet = /<([^:^>]+:[^:]+):[\d.]+>(.*)/;
|
||||
const re_extranet_g = /<([^:^>]+:[^:]+):[\d.]+>/g;
|
||||
|
|
@ -28,8 +30,6 @@ const storedPopupIds = {};
|
|||
const extraPageUserMetadataEditors = {};
|
||||
const extra_networks_tabs = {};
|
||||
/** Boolean flags used along with utils.js::waitForBool(). */
|
||||
// Set true when extraNetworksSetup completes.
|
||||
const extra_networks_setup_complete = {state: false};
|
||||
// Set true when we first load the UI options.
|
||||
const initialUiOptionsLoaded = {state: false};
|
||||
|
||||
|
|
@ -283,85 +283,131 @@ class ExtraNetworksTab {
|
|||
}
|
||||
}
|
||||
|
||||
async waitForServerPageReady() {
|
||||
// We need to wait for the page to be ready before we can fetch data.
|
||||
// After starting the server, on the first load of the page, if the user
|
||||
// immediately clicks a tab, then we will try to load the card data before
|
||||
// the server has even generated it.
|
||||
// We use status 503 to indicate that the page isnt ready yet.
|
||||
let ready = false;
|
||||
while (!ready) {
|
||||
try {
|
||||
await requestGetPromise(
|
||||
"./sd_extra_networks/page-is-ready",
|
||||
{extra_networks_tabname: this.extra_networks_tabname},
|
||||
);
|
||||
ready = true;
|
||||
} catch (error) {
|
||||
if (error.status === 503) {
|
||||
await new Promise(resolve => setTimeout(resolve, 250));
|
||||
} else {
|
||||
// We do not want to continue waiting if we get an unhandled error.
|
||||
throw new Error("Error checking page readiness:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
async waitForServerPageReady(
|
||||
max_attempts = EXTRA_NETWORKS_GET_PAGE_READY_MAX_ATTEMPTS,
|
||||
delay_ms = EXTRA_NETWORKS_REQUEST_GET_TIMEOUT_MS,
|
||||
) {
|
||||
/** 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.
|
||||
* It is possible to click on a tab before the server has any data ready for us.
|
||||
* Since clicking on tabs triggers a data request, there will be an error from
|
||||
* 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
|
||||
* 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})`;
|
||||
return new Promise((resolve, reject) => {
|
||||
let attempt = 0;
|
||||
const loop = () => {
|
||||
setTimeout(async() => {
|
||||
try {
|
||||
const response = JSON.parse(
|
||||
await requestGetPromise(
|
||||
"./sd_extra_networks/page-is-ready",
|
||||
{extra_networks_tabname: this.extra_networks_tabname},
|
||||
EXTRA_NETWORKS_REQUEST_GET_TIMEOUT_MS,
|
||||
)
|
||||
);
|
||||
if (response.ready === true) {
|
||||
return resolve();
|
||||
} else if (max_attempts !== 0 && attempt++ >= max_attempts) {
|
||||
return reject(`${err_prefix}: max attempts exceeded`);
|
||||
} else {
|
||||
setTimeout(() => loop(), delay_ms);
|
||||
}
|
||||
} catch (error) {
|
||||
return reject(`${err_prefix}: ${error}`);
|
||||
}
|
||||
}, 0);
|
||||
};
|
||||
return loop();
|
||||
});
|
||||
}
|
||||
|
||||
async onInitCardsData() {
|
||||
await this.waitForServerPageReady();
|
||||
try {
|
||||
await this.waitForServerPageReady();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return {};
|
||||
}
|
||||
|
||||
return JSON.parse(
|
||||
await requestGetPromise(
|
||||
"./sd_extra_networks/init-cards-data",
|
||||
{
|
||||
tabname: this.tabname,
|
||||
extra_networks_tabname: this.extra_networks_tabname,
|
||||
},
|
||||
)
|
||||
);
|
||||
const url = "./sd_extra_networks/init-cards-data";
|
||||
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));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
async onInitTreeData() {
|
||||
await this.waitForServerPageReady();
|
||||
try {
|
||||
await this.waitForServerPageReady();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return {};
|
||||
}
|
||||
|
||||
return JSON.parse(
|
||||
await requestGetPromise(
|
||||
"./sd_extra_networks/init-tree-data",
|
||||
{
|
||||
tabname: this.tabname,
|
||||
extra_networks_tabname: this.extra_networks_tabname,
|
||||
},
|
||||
)
|
||||
);
|
||||
const url = "./sd_extra_networks/init-tree-data";
|
||||
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));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
async onFetchCardsData(div_ids) {
|
||||
await this.waitForServerPageReady();
|
||||
try {
|
||||
await this.waitForServerPageReady();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return {};
|
||||
}
|
||||
|
||||
return JSON.parse(
|
||||
await requestGetPromise(
|
||||
"./sd_extra_networks/fetch-cards-data",
|
||||
{
|
||||
extra_networks_tabname: this.extra_networks_tabname,
|
||||
div_ids: div_ids,
|
||||
},
|
||||
)
|
||||
);
|
||||
const url = "./sd_extra_networks/fetch-cards-data";
|
||||
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));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
async onFetchTreeData(div_ids) {
|
||||
await this.waitForServerPageReady();
|
||||
try {
|
||||
await this.waitForServerPageReady();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return {};
|
||||
}
|
||||
|
||||
return JSON.parse(
|
||||
await requestGetPromise(
|
||||
"./sd_extra_networks/fetch-tree-data",
|
||||
{
|
||||
extra_networks_tabname: this.extra_networks_tabname,
|
||||
div_ids: div_ids,
|
||||
},
|
||||
)
|
||||
);
|
||||
const url = "./sd_extra_networks/fetch-tree-data";
|
||||
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));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
updateSearch(text) {
|
||||
|
|
@ -635,10 +681,11 @@ 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) {
|
||||
await v.load(show_prompt, show_neg_prompt);
|
||||
v.load(show_prompt, show_neg_prompt);
|
||||
} else {
|
||||
v.unload();
|
||||
}
|
||||
|
|
@ -960,7 +1007,6 @@ async function extraNetworksSetupTab(tabname) {
|
|||
}
|
||||
|
||||
async function extraNetworksSetup() {
|
||||
extra_networks_setup_complete.state = false;
|
||||
await waitForBool(initialUiOptionsLoaded);
|
||||
|
||||
await Promise.all([
|
||||
|
|
@ -969,8 +1015,6 @@ async function extraNetworksSetup() {
|
|||
]);
|
||||
|
||||
extraNetworksSetupEventDelegators();
|
||||
|
||||
extra_networks_setup_complete.state = true;
|
||||
}
|
||||
|
||||
onUiLoaded(extraNetworksSetup);
|
||||
|
|
|
|||
|
|
@ -261,9 +261,9 @@ function debounce(handler, timeout_ms) {
|
|||
};
|
||||
}
|
||||
|
||||
function waitForElement(selector) {
|
||||
function waitForElement(selector, timeout_ms) {
|
||||
/** Promise that waits for an element to exist in DOM. */
|
||||
return new Promise(resolve => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (document.querySelector(selector)) {
|
||||
return resolve(document.querySelector(selector));
|
||||
}
|
||||
|
|
@ -271,7 +271,7 @@ function waitForElement(selector) {
|
|||
const observer = new MutationObserver(mutations => {
|
||||
if (document.querySelector(selector)) {
|
||||
observer.disconnect();
|
||||
resolve(document.querySelector(selector));
|
||||
return resolve(document.querySelector(selector));
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -279,28 +279,46 @@ function waitForElement(selector) {
|
|||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
|
||||
if (isNumber(timeout_ms) && timeout_ms !== 0) {
|
||||
setTimeout(() => {
|
||||
observer.takeRecords();
|
||||
observer.disconnect();
|
||||
return reject(`timed out waiting for element: "${selector}"`);
|
||||
}, timeout_ms);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function waitForBool(o) {
|
||||
function waitForBool(o, timeout_ms) {
|
||||
/** Promise that waits for a boolean to be true.
|
||||
*
|
||||
* `o` must be an Object of the form:
|
||||
* { state: <bool value> }
|
||||
*
|
||||
* If timeout_ms is null/undefined or 0, waits forever.
|
||||
*
|
||||
* Resolves when (state === true)
|
||||
* Rejects when state is not True before timeout_ms.
|
||||
*/
|
||||
return new Promise(resolve => {
|
||||
let wait_timer;
|
||||
return new Promise((resolve, reject) => {
|
||||
if (isNumber(timeout_ms) && timeout_ms !== 0) {
|
||||
setTimeout(() => {
|
||||
clearTimeout(wait_timer);
|
||||
return reject("timed out waiting for bool");
|
||||
}, timeout_ms);
|
||||
}
|
||||
(function _waitForBool() {
|
||||
if (o.state) {
|
||||
return resolve();
|
||||
}
|
||||
setTimeout(_waitForBool, 100);
|
||||
wait_timer = setTimeout(_waitForBool, 100);
|
||||
})();
|
||||
});
|
||||
}
|
||||
|
||||
function waitForKeyInObject(o) {
|
||||
function waitForKeyInObject(o, timeout_ms) {
|
||||
/** Promise that waits for a key to exist in an object.
|
||||
*
|
||||
* `o` must be an Object of the form:
|
||||
|
|
@ -309,19 +327,29 @@ function waitForKeyInObject(o) {
|
|||
* k: <key to watch for>,
|
||||
* }
|
||||
*
|
||||
* Resolves when (k in obj)
|
||||
* If timeout_ms is null/undefined or 0, waits forever.
|
||||
*
|
||||
* Resolves when (k in obj).
|
||||
* Rejects when k is not found in obj before timeout_ms.
|
||||
*/
|
||||
return new Promise(resolve => {
|
||||
let wait_timer;
|
||||
return new Promise((resolve, reject) => {
|
||||
if (isNumber(timeout_ms) && timeout_ms !== 0) {
|
||||
setTimeout(() => {
|
||||
clearTimeout(wait_timer);
|
||||
return reject(`timed out waiting for key: ${o.k}`);
|
||||
}, timeout_ms);
|
||||
}
|
||||
(function _waitForKeyInObject() {
|
||||
if (o.k in o.obj) {
|
||||
return resolve();
|
||||
}
|
||||
setTimeout(_waitForKeyInObject, 100);
|
||||
wait_timer = setTimeout(_waitForKeyInObject, 100);
|
||||
})();
|
||||
});
|
||||
}
|
||||
|
||||
function waitForValueInObject(o) {
|
||||
function waitForValueInObject(o, timeout_ms) {
|
||||
/** Promise that waits for a key value pair in an Object.
|
||||
*
|
||||
* `o` must be an Object of the form:
|
||||
|
|
@ -331,10 +359,19 @@ function waitForValueInObject(o) {
|
|||
* v: <value at key for comparison>
|
||||
* }
|
||||
*
|
||||
* If timeout_ms is null/undefined or 0, waits forever.
|
||||
*
|
||||
* Resolves when obj[k] == v
|
||||
*/
|
||||
return new Promise(resolve => {
|
||||
waitForKeyInObject({k: o.k, obj: o.obj}).then(() => {
|
||||
let wait_timer;
|
||||
return new Promise((resolve, reject) => {
|
||||
if (isNumber(timeout_ms) && timeout_ms !== 0) {
|
||||
setTimeout(() => {
|
||||
clearTimeout(wait_timer);
|
||||
return reject(`timed out waiting for value: ${o.k}: ${o.v}`);
|
||||
}, timeout_ms);
|
||||
}
|
||||
waitForKeyInObject({k: o.k, obj: o.obj}, timeout_ms).then(() => {
|
||||
(function _waitForValueInObject() {
|
||||
|
||||
if (o.k in o.obj && o.obj[o.k] == o.v) {
|
||||
|
|
@ -342,6 +379,8 @@ function waitForValueInObject(o) {
|
|||
}
|
||||
setTimeout(_waitForValueInObject, 100);
|
||||
})();
|
||||
}).catch((error) => {
|
||||
return reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -374,26 +413,32 @@ function requestGet(url, data, handler, errorHandler) {
|
|||
xhr.send(js);
|
||||
}
|
||||
|
||||
function requestGetPromise(url, data) {
|
||||
function requestGetPromise(url, data, timeout_ms) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let xhr = new XMLHttpRequest();
|
||||
let args = Object.keys(data).map(k => {
|
||||
return encodeURIComponent(k) + "=" + encodeURIComponent(data[k]);
|
||||
const xhr = new XMLHttpRequest();
|
||||
const args = Object.entries(data).map(([k, v]) => {
|
||||
return `${encodeURIComponent(k)}=${encodeURIComponent(v)}`;
|
||||
}).join("&");
|
||||
xhr.open("GET", url + "?" + args, true);
|
||||
|
||||
xhr.onload = () => {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
resolve(xhr.responseText);
|
||||
return resolve(xhr.responseText);
|
||||
} else {
|
||||
reject({status: xhr.status, response: xhr.responseText});
|
||||
return reject({status: xhr.status, response: xhr.responseText});
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = () => {
|
||||
reject({status: xhr.status, response: xhr.responseText});
|
||||
return reject({status: xhr.status, response: xhr.responseText});
|
||||
};
|
||||
|
||||
xhr.ontimeout = () => {
|
||||
return reject(`Request for ${url} timed out.`);
|
||||
};
|
||||
|
||||
const payload = JSON.stringify(data);
|
||||
xhr.open("GET", `${url}?${args}`, true);
|
||||
xhr.timeout = timeout_ms;
|
||||
xhr.send(payload);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue