Skip to content

Instantly share code, notes, and snippets.

@asontu
Last active June 28, 2021 11:36
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save asontu/266d5dfa48df0a7530889be2f5d79efe to your computer and use it in GitHub Desktop.
Save asontu/266d5dfa48df0a7530889be2f5d79efe to your computer and use it in GitHub Desktop.
Functions to easily make a MutationObserver Promise in JavaScript. Usage described on my blog: https://asontu.github.io/2020/12/30/mutationobserver-promise-made-easy.html
/**
* Runs "trigger" function after setting up MutationObserver and (optionally) Timeout
* that respectively resolve or reject the Promise. The resolve function gets an array
* (not NodeList) of elements that were added as argument. If the trigger function
* returns true the Promise is immediately resolved with an empty array, without waiting
* for a Mutation.
*
* @param {Function} trigger Function to run after setting up MutationObserver and Timeout.
* @param {Object} watch DOM Element to watch for Mutations.
* @param {string} [query=*] Selector query that elements added in the Observed Mutation must match.
* @param {Object} [options={attributes:false, childList: true, subtree: false}] Options passed to MutationObserver.
* @param {int=} timeout Milliseconds after which the Promise should reject. If ommitted no Timeout is set.
* @returns {Promise} Promise object represents the added elements
*/
function mop(trigger, watch, query, options, timeout) {
return new Promise((resolve, reject) => {
let timer;
let observer = new MutationObserver((mutationList) => {
let any = searchMutationListFor(mutationList, query || '*');
if (query && !any) {
return;
}
observer.disconnect();
clearTimeout(timer);
resolve(any);
});
observer.observe(watch, options || {attributes:false, childList: true, subtree: false});
if (timeout) {
timer = setTimeout(() => {
observer.disconnect();
reject(new Error('Timed out observing mutation'));
}, timeout);
}
if (trigger()) {
observer.disconnect();
clearTimeout(timer);
resolve([]);
}
});
}
/**
* Searches mutations from MutationObserver for elements matching a query. Returns
* either false or an array with found matching elements.
*
* @param {Object[]} mutationList List of Mutations as passed to MutationObserver's callback.
* @param {string} [query] Selector query that elements added in the Observed Mutation must match.
* @returns {false|Object[]}
*/
function searchMutationListFor(mutationList, query) {
if (!mutationList.length) {
return false;
}
let foundNodes = [];
function findNodes(addedNode) {
if (addedNode.matches(query)) {
foundNodes.push(addedNode);
}
foundNodes = foundNodes.concat(Array.from(addedNode.querySelectorAll(query)));
}
for (let m = 0; m < mutationList.length; m++) {
if (!mutationList[m].addedNodes.length) continue;
Array.from(mutationList[m].addedNodes)
.filter(nod => nod.nodeType == 1)
.forEach(findNodes);
}
if (!foundNodes.length) {
return false;
}
return foundNodes;
}
@jwilson8767
Copy link

Would be good if this had some jsdoc for params / usage. Gists work best when self-documenting as they will often be separated from your blog post. Otherwise, very nice!

@asontu
Copy link
Author

asontu commented Dec 30, 2020

@jwilson8767 Thanks! Added jsdoc for the params.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment