简体   繁体   中英

Javascript for loop, wait for function callback

I have a for loop that calls a function inside of itself. The function has a callback, however the for loop does not wait for it to finish and continues working.

The for loop actually finishes so quickly that the next action, serving a document that was supposed to be populated inside the for loop, is done before the first function call is completed.

Here is the content of the call I'm actually using, where images is an array holding external URLs to images:

// New jsPDF Document
var doc = new jsPDF();

doc.setFontSize(12);

// For each image we add the image to the document as an image
for (var index = 0; index < images.length; index++) {

    // We only add pages after the first one
    if (index !== 0) {
        doc.addPage();
    }

    // This puts the URL of the active element at the top of the document
    doc.text(35, 25, images[index].path);

    // Call to our function, this is the 'skipped' portion
    convertImgToDataURLviaCanvas(images[index].path, function(base64Img) {
        console.log('An image was processed');
        doc.addImage(base64Img, 15, 40, 180, 180);
    });
}

doc.save('demo.pdf');
console.log('Document served!');

We get the image URLs from our array, and add everything. The convertImgToDataURLviaCanvas function is here:

// Gets an URL and gives you a Base 64 Data URL
function convertImgToDataURLviaCanvas(url, callback, outputFormat){
    var img = new Image();
    img.crossOrigin = 'Anonymous';
    img.onload = function() {
        var canvas = document.createElement('CANVAS');
        var ctx = canvas.getContext('2d');
        var dataURL;
        canvas.height = this.height;
        canvas.width = this.width;
        ctx.drawImage(this, 0, 0);
        dataURL = canvas.toDataURL(outputFormat);
        callback(dataURL);
        canvas = null; 
    };
    img.src = url;
}

In the former examples even the line doc.text(35, 25, images[index].path); does properly write the URLs to the top of the page. Since that is contained in the array and works along the iterator. However, the for loop is completely done before the first image has been added to our document!

With the console.log you would see: 'Document served!' before the first 'An image was processed' . The goal would be for it to be reversed, with every 'An image was processed' being outputted before Document served! appeared.

How can I achieve this functionality?

To guarantee the order of images, using promises is simple

var promises = images.map(function(image, index) {
    return new Promise(function(resolve) {
        convertImgToDataURLviaCanvas(image.path, function(base64Img) {
            resolve(base64Img);
        });
    });
}
Promise.all(promises).then(function(results) {
    results.forEach(function(base64Img, index) {
        if (index !== 0) {
            doc.addPage();
        }
        doc.text(35, 25, images[index].path);
        console.log('An image was processed');
        doc.addImage(base64Img, 15, 40, 180, 180);
    });
    doc.save('demo.pdf');
    console.log('Document served!');
});

For completeness (unchecked though) - the non promise way to guarantee image order and have the addPage/text in the correct place

var base64 = new Array(images.length); // base64 images held here
images.forEach(function(image, index) {
    // Call to our function, this is the 'skipped' portion
    convertImgToDataURLviaCanvas(image.path, function(base64Img) {
        console.log('An image was processed');
        // We only add pages after the first one
        base64[index] = base64Img;
        done++;
        if (done == images.length) {
            base64.forEach(function(img64, indx) {
                if (indx !== 0) {
                    doc.addPage();
                }
                // This puts the URL of the active element at the top of the document
                doc.text(35, 25, images[indx].path);
                doc.addImage(img64, 15, 40, 180, 180);
            }
            doc.save('demo.pdf');
            console.log('Document served!');
        }
    });
}

One way to do it is as follows

 
 
 
  
  var done = 0; // added this to keep counf of finished images for (var index = 0; index < images.length; index++) { // We only add pages after the first one if (index !== 0) { doc.addPage(); } // This puts the URL of the active element at the top of the document doc.text(35, 25, images[index].path); // Call to our function, this is the 'skipped' portion convertImgToDataURLviaCanvas(images[index].path, function(base64Img) { console.log('An image was processed'); doc.addImage(base64Img, 15, 40, 180, 180); // added this code, checks number of finished images and finalises the doc when done == images.length done++; if (done == images.length) { // move the doc.save here doc.save('demo.pdf'); console.log('Document served!'); } // end of new code }); }
 
  

Using promises, it's easy as ...

 
 
 
  
  // For each image we add the image to the document as an image var promises = images.map(function(image, index) { // We only add pages after the first one if (index !== 0) { doc.addPage(); } // This puts the URL of the active element at the top of the document doc.text(35, 25, image.path); // Call to our function, this is the 'skipped' portion // this returns a Promise that is resolved in the callback return new Promise(function(resolve) { convertImgToDataURLviaCanvas(image.path, function(base64Img) { console.log('An image was processed'); doc.addImage(base64Img, 15, 40, 180, 180); resolve(index); // doesn't really matter what is resolved }); }); } Promise.all(promises).then(function(results) { doc.save('demo.pdf'); console.log('Document served!'); });
 
  

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM