Chaining promises by iterating an array

Viewed 297

Is there a way to chain promises by iterating through an array?

For example, I'm trying to convert the content of multiple tabs into their own separate pdf pages using jsPDF and html2canvas.

const pdf = new jsPDF('l');

let index = 1;
let input = document.getElementById('print');

const formatPage = (canvas) => {
    const imgData = canvas.toDataURL('image/jpg');
    pdf.addImage(imgData, 'JPEG', 10, 10, 200, 100, '', 'FAST', 0);
      
}

const nextPage = () => {
    pdf.addPage();
    document.getElementById(`${tabs[index]}`).click();
    index++;
    input = document.getElementById('print');
}

html2canvas(input)
      .then(canvas => {
        formatPage(canvas);
        nextPage();
        return html2canvas(input);
      })
      .then(canvas => {
        formatPage(canvas);
        nextPage();
        return html2canvas(input);
      })
      .then(canvas => {
        formatPage(canvas);
        pdf.save('download.pdf');
      })

Is there a way to do the same thing through iteration of the tabs array? I want to be able to chain a dynamic number of promises instead of hard coding it because for example, every page might not have the same number of tabs.

Solution:

I couldn't get it to work with Promise.all() but this is how I did it.

async generatePages() {
      for(let index = 0; index !== tabs.length; index++) {
           document.getElementById(`${tabs[index]}`).click();
           input = document.getElementById('print');
           await this.processPage(input, index, tabs.length - 1);
    }
}

processPage(input, index, end) {
    const formatPage = (canvas) => {
      const imgData = canvas.toDataURL('image/jpg');
      pdf.addImage(imgData, 'JPEG', 10, 10, 200, 100, '', 'FAST', 0);
    }

    return html2canvas(input).then(canvas => {
      formatPage(canvas);

      if(index !== end) {
        pdf.addPage();
      } else {
        pdf.save('download.pdf');
      }
    })
  }
3 Answers

What you can do is create an array of Promise objects, and then use Promise.all to resolve them.

let promises = []

promises.push(promise1)
promises.push(promise2)
promises.push(promise3)

Promise.all(promises).then(results => {
  results.forEach(result => {
  // do something with results
  })
})

I haven't tested this but, from what you want I could possibly recommend the following steps:

  • You want to iterate through the tabs
  • You want to get all content from each tab (I'm not sure about this one)
  • You create a promise with html2canvas with each content
  • You process all the promises for each content with Promise.promiseAll
  • You iterate the resolved promises
  • You format them and add more pages if it is not the last one, otherwise you save the PDF

This is approximately how the implementation should look like:

const tabsElements = document.querySelectorAll(`.tab`);
const tabsContentPromises = tabsElements.reduce((acc, tabEl) => {
  const input = tabEl.querySelector('input');

  return acc.push(html2canvas(input));
}, []);

const pdf = new jsPDF('l');

const formatPage = (canvas) => {
  const imgData = canvas.toDataURL('image/jpg');
  pdf.addImage(imgData, 'JPEG', 10, 10, 200, 100, '', 'FAST', 0);
}

Promise.promiseAll(tabsContentPromises).then(tabsContentCanvas => {
  tabsContentCanvas.forEach((contentCanvas, index) => {
    formatPage(contentCanvas);
    
    if (index !== (tabsContentCanvas.length - 1)) {  
      pdf.addPage();
    } else {
      pdf.save('download.pdf');
    }
  });
});

If I understand correctly, Promise.all is certainly the way to go, but you need to get an array of promises, so you need to map your array of tabs to their processed promise:

generatePages() {
    return Promise.all(tabs.map((tab, index) => {
       document.getElementById(`${tab}`).click();
       input = document.getElementById('print');
       return this.processPage(input, index, tabs.length - 1);
    }));
}
Related