title: waiting for promises inside a foreach
body: <p>I am new to promises. I get how <em>one</em> works.</p>
  
  <p>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).</p>
  
  <p>I want the function to return only after all promises <code>zip.file().async().then(...)</code> are resolved. E.g. have a <code>domDocument</code>, then perform a series of asynchronous steps on it, then pick up the modifed <code>domDocument</code> afterwards. IT might be possible to modify the area that calls this function to use a <code>then</code> (e.g. <code>processFile(folder,zip,node,dom).then(function(result)</code>... if need be.</p>
  
  <pre><code>// 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" &amp;&amp; (-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") {
                      // &lt;link href="file.css"&gt; ==&gt; &lt;style&gt;(file contents)&lt;/style&gt;
                      var style = document.createElement("style");
                      style.appendChild(document.createTextNode(content));
                      instance.parentNode.replaceChild(style,instance);
                  } else if (element.type === "base64") {
                      // &lt;img src="file.gif"&gt; ==&gt; &lt;img src="data:image/gif;base64,(image-contents)"&gt;
                      var extn = value.substr(value.lastIndexOf(".") + 1).toLowerCase();
                      instance.setAttribute("src", "data:image/" + extn + ";base64," + content);
                  } else {
                      // &lt;sript src="file.js"&gt;&lt;/script&gt; =&gt; &lt;script&gt;(file-contents&lt;/script&gt;)
                      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;
  </code></pre>
  
  <p>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 </p>
  
  <p><strong>/edit:</strong></p>
  
  <p>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. </p>
  
  <pre><code>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"}
      ]);
  }
  </code></pre>
  
  <p>still grasping at the concepts in async and promises</p>