I am new to promises. I get how one works.
I have a function that is converting a html document with external references (images, styles, scripts) into a file with those resources embedded. it requires reading the resources from an asynchronous source (a JSZip instance).
I want the function to return only after all promises zip.file().async().then(...)
are resolved. E.g. have a domDocument
, then perform a series of asynchronous steps on it, then pick up the modifed domDocument
afterwards. IT might be possible to modify the area that calls this function to use a then
(e.g. processFile(folder,zip,node,dom).then(function(result)
... if need be.
// folder is set elsewhere
// zipObject is a JSZip instance
// node is an xml document which contains path references to files mentioned in the html
// domDocument is a DOMParser instance which is the html file
// these are the queryselectors for elements I want to match and replace in the html
[
{"selector":"script[src]:not([src*='//'])","attribute":"src","type":"string"},
{"selector":"link[href$='.css']:not([href*='//'])","attribute":"href","type":"string"},
{"selector":"img:not([src*='//'])","attribute":"src","type":"base64"},
{"selector":"a[target='_blank']:not([href*='://'])","type":"link"}
].forEach(function(element) {
// grab all instances of a particular selector
[].forEach.call(domDocument.querySelectorAll(element.selector), function(instance) {
if (element.type === "link") { // replace link with plain text
var tNode = document.createTextNode(instance.textContent);
instance.parentNode.replaceChild(tNode, instance);
} else { // need to load from the linked document
var value = unescape(instance.getAttribute(element.attribute)),
path = null;
// images that are already encoded can be ignored
if (element.type==="base64" && (-1!==value.indexOf("data:image"))) return;
if (value.indexOf("../")==0) { // resolve dependancy filename
var dep = node.parentNode.querySelector("dependency");
if (null !== dep) {
var ref = node.parentNode.parentNode.querySelector("resource[identifier='" + dep.getAttribute("identifierref") + "']");
if (null !== ref) {
path = ref.querySelector("file[href]").getAttribute("href");
}
}
} else { // resolve path from filename
path = folder + value;
}
// perform the (asynchronous) modification of the document
zipObject.file(path).async(element.type).then(function success(content) {
if (element.attribute === "href") {
// <link href="file.css"> ==> <style>(file contents)</style>
var style = document.createElement("style");
style.appendChild(document.createTextNode(content));
instance.parentNode.replaceChild(style,instance);
} else if (element.type === "base64") {
// <img src="file.gif"> ==> <img src="data:image/gif;base64,(image-contents)">
var extn = value.substr(value.lastIndexOf(".") + 1).toLowerCase();
instance.setAttribute("src", "data:image/" + extn + ";base64," + content);
} else {
// <sript src="file.js"></script> => <script>(file-contents</script>)
instance.removeAttribute(element.attribute);
instance.appendChild(document.createTextNode(content));
}
}, function error (e) {
console.log("ConvertZipForHtml Error", e);
// leave it alone I guess
});
}
});
});
// want this to return the MODIFIED version of the string
return domDocument;
I can see that each iteration of the first array needs to be a promise and that the whole function should only return after all of the those promises resolve, but I can't figure out how to
/edit:
I think I see (thanks @Bergi). If I make a promise all which returns a promise for each item which only resolves after JSZip's async function resolves, then it seems to work asynchronously and my outer function can use a .then() after all promises are finished.
function myFunction(args) {
function replaceElements(elements) {
return Promise.all(elements.map(selectElements));
}
function selectElements(element) {
return new Promise(function (resolve, reject) {
var myNodeList = domDocument.querySelectorAll(element.selector);
for (var i = myNodeList.length; i--;) { // (generally is faster than forwards!)
var instance = myNodeList[i];
// -- snip --
zipObject.file(path).async(element.type).then(function success(content) {
// -- snip --
resolve(instance);
})
}
});
}
return replaceElements([
{"selector":"script[src]:not([src*='//'])", "attribute":"src", "type":"string"},
{"selector":"link[href$='.css']:not([href*='//'])", "attribute":"href", "type":"string"},
{"selector":"img:not([src*='//'])", "attribute":"src", "type":"base64"},
{"selector":"a[target='_blank']:not([href*='://'])", "type":"link"}
]);
}
still grasping at the concepts in async and promises