0

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?

5
  • 1
    Is expected result to wait for doc.addPage(); to complete before calling convertImgToDataURLviaCanvas ? Commented Nov 21, 2015 at 4:28
  • @guest271314 the expected result is making sure the convertImgToDataURLviaCanvas are called in order, do their callback doc.addPage(); and then, and only then, let the next loop iteration begin. Commented Nov 21, 2015 at 4:30
  • Not a complete solution but just checking: Did you try putting doc.save() part into convertImgToDataURLviaCanvas's callback under for loop? Commented Nov 21, 2015 at 4:33
  • doc.addPage() is callback for convertImgToDataURLviaCanvas ? convertImgToDataURLviaCanvas is called first , then doc.addPage() is called ? "With the console.log you would see: 'Document served!' before the first 'An image was processed'. " console.log('Document served!'); is synchronous Commented Nov 21, 2015 at 4:33
  • @guest271314 Yes, to be specific doc.addPage() is inside of the callback. convertImgToDataURLviaCanvas(images[index].path, function(base64Img) { 'This is the callback' });. @alix, that would just try to save as many times as there are files. I suppose the last callback would actually be the proper file, but I don't think that way leads the proper way to a solution. Commented Nov 21, 2015 at 4:36

1 Answer 1

4

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!');
});

Sign up to request clarification or add additional context in comments.

13 Comments

Sweet! Give me a second to test this out.
the question was about doc.save being called too early - any rewrite to the logic of where doc.addPage should be is irrelevant and beyond the scope of the question as far as I'm concerned
Yeah I'm still chekcing it out @JaromandaX, the addPage and doc.text do need to be added to the callback, otherwise all the pages are added at first.
if you're open to using promises, adding the images in the right order is quite easy - see edited answer
@Erick - I've added a non-promise guaranteed order answer - I may edit the answer to just include the last two versions which should address all issues even those not originally referenced in the question
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.