-
-
Save jwilson8767/db379026efcbd932f64382db4b02853e to your computer and use it in GitHub Desktop.
// MIT Licensed | |
// Author: jwilson8767 | |
/** | |
* Waits for an element satisfying selector to exist, then resolves promise with the element. | |
* Useful for resolving race conditions. | |
* | |
* @param selector | |
* @returns {Promise} | |
*/ | |
export function elementReady(selector) { | |
return new Promise((resolve, reject) => { | |
let el = document.querySelector(selector); | |
if (el) { | |
resolve(el); | |
return | |
} | |
new MutationObserver((mutationRecords, observer) => { | |
// Query for elements matching the specified selector | |
Array.from(document.querySelectorAll(selector)).forEach((element) => { | |
resolve(element); | |
//Once we have resolved we don't need the observer anymore. | |
observer.disconnect(); | |
}); | |
}) | |
.observe(document.documentElement, { | |
childList: true, | |
subtree: true | |
}); | |
}); | |
} |
import { elementReady } from "es6-element-ready"; | |
// Simple usage to delete an element if/when it exists: | |
elementReady('#someWidget').then((someWidget)=>{someWidget.remove();}); |
Thanks @jwilson8767 for the last script,
it did work for me. also i m not sure about if it s more efficient than using mutationobserver..
Crazy hard to understand this script though, quite complicate.
i personaly removed the async and did a return Promise.resolve(el); for the first return.
also added a settimeout to test if the script run more than 4second to stop it in case it didnt find all matches.
@bezborodow Regarding performance issues,
elementReady()
should definitely be taken as a convenience function, I still use it occasionally but only where it is expected to run only a few times in the lifecycle of a page (such as to detect when a component's root element has been added to the page). MutationObservers as a whole aren't amazing performance-wise, really. For much better performance (while still working on arbitrary selectors), I'd actually recommend using a sort of long-polling strategy via setInterval. You could either create one setInterval per selector, or a shared setInterval and an array of outstanding selectors to match against. Here's an example of the latter:// dict of {selector: [promise, resolve]} const pendingElements = {}; let pendingElementsInterval; const elementReadyBatchedFrequency = 100; // ms /** * Wait for an element to be ready using a querySelector * * @param selector {string} * @param containerEl {Element} optional container element to search within * @returns {Promise<Element>} */ async function elementReadyBatched(selector, containerEl = document){ let el = document.querySelector(selector); if (el) { return el; } // group outstanding requests if (pendingElements[selector]) { const [promise, _] = pendingElements[selector]; delete pendingElements[selector]; return promise; } let resolve; const promise = new Promise(r => resolve = r); pendingElements[selector] = [promise, resolve]; if (!pendingElementsInterval) { pendingElementsInterval = setInterval(() => { for (const [_selector, [_, _resolve]] of Object.entries(pendingElements)) { const el = containerEl.querySelector(_selector); if (el) { _resolve(el); delete pendingElements[_selector]; } } if (!Object.keys(pendingElements).length) { clearInterval(pendingElementsInterval); pendingElementsInterval = null; } }, elementReadyBatchedFrequency); } return pendingElements[selector][0]; }edit: yes, I know this is very late in coming. Still <3 you.
Thanks @jwilson8767 for the last script,
it did work for me. also i m not sure about if it s more efficient than using mutationobserver..
Crazy hard to understand this script though, quite complicate.
i personaly removed the async and did a return Promise.resolve(el); for the first return.
also added a settimeout to test if the script run more than 4second to stop it in case it didnt find all matches.
@bezborodow Regarding performance issues,
elementReady()
should definitely be taken as a convenience function, I still use it occasionally but only where it is expected to run only a few times in the lifecycle of a page (such as to detect when a component's root element has been added to the page). MutationObservers as a whole aren't amazing performance-wise, really. For much better performance (while still working on arbitrary selectors), I'd actually recommend using a sort of long-polling strategy via setInterval. You could either create one setInterval per selector, or a shared setInterval and an array of outstanding selectors to match against. Here's an example of the latter:// dict of {selector: [promise, resolve]} const pendingElements = {}; let pendingElementsInterval; const elementReadyBatchedFrequency = 100; // ms /** * Wait for an element to be ready using a querySelector * * @param selector {string} * @param containerEl {Element} optional container element to search within * @returns {Promise<Element>} */ async function elementReadyBatched(selector, containerEl = document){ let el = document.querySelector(selector); if (el) { return el; } // group outstanding requests if (pendingElements[selector]) { const [promise, _] = pendingElements[selector]; delete pendingElements[selector]; return promise; } let resolve; const promise = new Promise(r => resolve = r); pendingElements[selector] = [promise, resolve]; if (!pendingElementsInterval) { pendingElementsInterval = setInterval(() => { for (const [_selector, [_, _resolve]] of Object.entries(pendingElements)) { const el = containerEl.querySelector(_selector); if (el) { _resolve(el); delete pendingElements[_selector]; } } if (!Object.keys(pendingElements).length) { clearInterval(pendingElementsInterval); pendingElementsInterval = null; } }, elementReadyBatchedFrequency); } return pendingElements[selector][0]; }edit: yes, I know this is very late in coming. Still <3 you.
Thanks @jwilson8767 for the last script,
it did work for me. also i m not sure about if it s more efficient than using mutationobserver..
Crazy hard to understand this script though, quite complicate.
i personaly removed the async and did a return Promise.resolve(el); for the first return.
also added a settimeout to test if the script run more than 4second to stop it in case it didnt find all matches.
@bezborodow Regarding performance issues,
elementReady()
should definitely be taken as a convenience function, I still use it occasionally but only where it is expected to run only a few times in the lifecycle of a page (such as to detect when a component's root element has been added to the page). MutationObservers as a whole aren't amazing performance-wise, really. For much better performance (while still working on arbitrary selectors), I'd actually recommend using a sort of long-polling strategy via setInterval. You could either create one setInterval per selector, or a shared setInterval and an array of outstanding selectors to match against. Here's an example of the latter:// dict of {selector: [promise, resolve]} const pendingElements = {}; let pendingElementsInterval; const elementReadyBatchedFrequency = 100; // ms /** * Wait for an element to be ready using a querySelector * * @param selector {string} * @param containerEl {Element} optional container element to search within * @returns {Promise<Element>} */ async function elementReadyBatched(selector, containerEl = document){ let el = document.querySelector(selector); if (el) { return el; } // group outstanding requests if (pendingElements[selector]) { const [promise, _] = pendingElements[selector]; delete pendingElements[selector]; return promise; } let resolve; const promise = new Promise(r => resolve = r); pendingElements[selector] = [promise, resolve]; if (!pendingElementsInterval) { pendingElementsInterval = setInterval(() => { for (const [_selector, [_, _resolve]] of Object.entries(pendingElements)) { const el = containerEl.querySelector(_selector); if (el) { _resolve(el); delete pendingElements[_selector]; } } if (!Object.keys(pendingElements).length) { clearInterval(pendingElementsInterval); pendingElementsInterval = null; } }, elementReadyBatchedFrequency); } return pendingElements[selector][0]; }edit: yes, I know this is very late in coming. Still <3 you.
Thanks @jwilson8767 for the last script,
it did work for me. also i m not sure about if it s more efficient than using mutationobserver..
Crazy hard to understand this script though, quite complicate.
i personaly removed the async and did a return Promise.resolve(el); for the first return.
also added a settimeout to test if the script run more than 4second to stop it in case it didnt find all matches.
@bezborodow Regarding performance issues,
elementReady()
should definitely be taken as a convenience function, I still use it occasionally but only where it is expected to run only a few times in the lifecycle of a page (such as to detect when a component's root element has been added to the page). MutationObservers as a whole aren't amazing performance-wise, really. For much better performance (while still working on arbitrary selectors), I'd actually recommend using a sort of long-polling strategy via setInterval. You could either create one setInterval per selector, or a shared setInterval and an array of outstanding selectors to match against. Here's an example of the latter:// dict of {selector: [promise, resolve]} const pendingElements = {}; let pendingElementsInterval; const elementReadyBatchedFrequency = 100; // ms /** * Wait for an element to be ready using a querySelector * * @param selector {string} * @param containerEl {Element} optional container element to search within * @returns {Promise<Element>} */ async function elementReadyBatched(selector, containerEl = document){ let el = document.querySelector(selector); if (el) { return el; } // group outstanding requests if (pendingElements[selector]) { const [promise, _] = pendingElements[selector]; delete pendingElements[selector]; return promise; } let resolve; const promise = new Promise(r => resolve = r); pendingElements[selector] = [promise, resolve]; if (!pendingElementsInterval) { pendingElementsInterval = setInterval(() => { for (const [_selector, [_, _resolve]] of Object.entries(pendingElements)) { const el = containerEl.querySelector(_selector); if (el) { _resolve(el); delete pendingElements[_selector]; } } if (!Object.keys(pendingElements).length) { clearInterval(pendingElementsInterval); pendingElementsInterval = null; } }, elementReadyBatchedFrequency); } return pendingElements[selector][0]; }edit: yes, I know this is very late in coming. Still <3 you.
Crazy hard to understand this script though, quite complicate.
i personaly removed the async and did a return Promise.resolve(el); for the first return.
also added a settimeout to test if the script run more than 4second to stop it in case it didnt find all matches.