0

I have a promise that seemed to work before but not now.

I think I have everything in place but after the .get is called, it never gets to the for loop.

It jumps to the end and out of the promise, then back to the return (inside the .get) then to the resolve and then out.

If there were an error, it should have jumped to the catch but didn't, so how does it miss the for loop?

Here is the code:

    function readAllImagesFromPouch(id, imageDisplay) {

       return new Promise(function (resolve, reject) {

          var startElement = document.getElementById(imageDisplay);
          var image = "";

          // Get all attachments for this id

          DB_TaskImages.get(id, { attachments: true }).then(function (doc) {

         for (var key in doc._attachments) {

            DB_TaskImages.getAttachment(doc._id, key).then(function (blob) {
               var img = document.createElement('img');
               blob = resizeImage(blob, "100", "60");
               var url = URL.createObjectURL(blob);
               img.src = url;
               //alert(img.outerHTML);

               //startElement.appendChild(img);
               $(startElement).append("<div class='row' style='border:1px solid black'><div class='col-xs-10'>" +
                           img.outerHTML +
                           "</div>" +
                           "<div class='col-xs-1' style='padding-top:20px'>" +
                           "<img src='../Images/delete.png' alt='Delete'  class='taskimage'>" +
                           "</div></div>"
                        );
               return;
            }).catch(function () {
               console.log("No attachment for doc._id: " + doc._id + " and key: " + key);
            })
         }
         return;
          }).then(function () {
            resolve();
          }).catch(function (err) {
         console.log("Image not there for id: " + id);
         showMsg("Image not there for id: " + id);
         reject(err);
          })
       });         // end of promise
    }

And it is called from this code:

    readAllImagesFromPouch("006", "divImages").then(function () {
    }).catch(function (err) {
       console.log("In catch for readAllImagesFromPouch with err: " + err);
    })

Thanks,

Tom

tshad
  • 335
  • 2
  • 4
  • 18
  • I found the issue. It had to do with an error in the for loop. It should have been doc.attachments (not sure why it was that way since the Pouch docs had it with an underscore - may have been how I saved it). I would be curious if my promise is correct and that I have all the returns, resolves and rejects in the correct places. Still trying to get a handle on this. – tshad Mar 29 '17 at 00:30
  • no, you still have the Promise constructor anti-pattern, and all your code in `for ... in` loop will execute after the promise is resolved ... and your `$(startElement).append` could happen in any order – Jaromanda X Mar 29 '17 at 00:40
  • Avoid the [`Promise` constructor antipattern](http://stackoverflow.com/q/23803743/1048572?What-is-the-promise-construction-antipattern-and-how-to-avoid-it)! – Bergi Mar 29 '17 at 01:06

2 Answers2

0

Firstly, avoid the promise constructor anti-pattern. As DB_TaskImages.get returns a promise, you don't need to create one

Secondly, your for...in loop kicks off a bunch of asynchronous tasks - but you don't actually wait for them to complete

A possible rewrite of your code is:

function readAllImagesFromPouch(id, imageDisplay) {

    var startElement = document.getElementById(imageDisplay);
    var image = "";

    // Get all attachments for this id

    return DB_TaskImages.get(id, {
        attachments: true
    })
    .then(function(doc) {
        // a promise to chain to - all following tasks will be performed in series, not parallel - this can be changed
        var promise = Promise.resolve();
        Object.keys(doc._attachments).forEach(function(key) {
            promise = promise.then(function() {
                return DB_TaskImages.getAttachment(doc._id, key)
                .then(function(blob) {
                    var img = document.createElement('img');
                    blob = resizeImage(blob, "100", "60");
                    var url = URL.createObjectURL(blob);
                    img.src = url;
                    //alert(img.outerHTML);

                    //startElement.appendChild(img);
                    $(startElement).append("<div class='row' style='border:1px solid black'><div class='col-xs-10'>" +
                                           img.outerHTML +
                                           "</div>" +
                                           "<div class='col-xs-1' style='padding-top:20px'>" +
                                           "<img src='../Images/delete.png' alt='Delete'  class='taskimage'>" +
                                           "</div></div>"
                                          );
                    return;
                })
                .catch(function() {
                    console.log("No attachment for doc._id: " + doc._id + " and key: " + key);
                })
            });
        });
        return promise;
    })
    .catch(function(err) {
        console.log("Image not there for id: " + id);
        showMsg("Image not there for id: " + id);
        throw err;
    })
}

The above code will call DB_TaskImages.getAttachment(doc._id, key) in series.

If you can and want to execute DB_TaskImages.getAttachment(doc._id, key) in parallel, the code needs a little more work

function readAllImagesFromPouch(id, imageDisplay) {

    var startElement = document.getElementById(imageDisplay);
    var image = "";

    // Get all attachments for this id

    return DB_TaskImages.get(id, {
        attachments: true
    })
    .then(function(doc) {
        return Promise.all(Object.keys(doc._attachments)
            .map(function(key) {
                return DB_TaskImages.getAttachment(doc._id, key)
                // an error here should NOT reject the Promise.all, so handle the error and return undefined
                .catch(function() {
                    console.log("No attachment for doc._id: " + doc._id + " and key: " + key);
                })
            })
        );
    })
    .then(function(blobs) {
        return blobs.filter(function(blob) {
            // a failed .getAttachment results in an undefined result here, so ignore it
            return blob !== undefined;
        }).forEach(function(blob) {
            var img = document.createElement('img');
            blob = resizeImage(blob, "100", "60");
            var url = URL.createObjectURL(blob);
            img.src = url;
            $(startElement).append(
                "<div class='row' style='border:1px solid black'>" +
                    "<div class='col-xs-10'>" +
                        img.outerHTML +
                    "</div>" +
                    "<div class='col-xs-1' style='padding-top:20px'>" +
                        "<img src='../Images/delete.png' alt='Delete'  class='taskimage'>" +
                    "</div>" +
                "</div>"
            );
        });
    })
    .catch(function(err) {
        console.log("Image not there for id: " + id);
        showMsg("Image not there for id: " + id);
        throw err;
    })
}
Jaromanda X
  • 53,868
  • 5
  • 73
  • 87
  • You'll need `for (let key…` or an IIFE – Bergi Mar 29 '17 at 01:07
  • I went for `Object.keys(doc._attachments).forEach(function(key) {` instead – Jaromanda X Mar 29 '17 at 01:17
  • @JaromandaX: I am looking at your series example now. I am getting errors from the getAttachment for each image, but assume I missed something and am playing with it. I may need to add the blobutil promise from the other question because of how the data was stored and don't know if that will bite me or not but I will worry about that later. You learn something new every day. :) Thanks, – tshad Mar 29 '17 at 23:28
  • @JaromandaX: the error wasn't in the getAttachments but in the resizeImage function. In the other post you can see that I had changed this to a promise (with some other changes). I am going to look at your other sample as that looks like it might solve my issue. I like that it was done a little different so I can really look at them and see what is happening. I also want to add another answer to show the changes I made to the resizeImage function to turn it into a promise. The onload was an issue and would like to see how to handle that. Thanks, – tshad Mar 30 '17 at 00:03
0

This may not be the correct way to do this but I wanted to show the code I changed to see if it was the correct way to do this. This is really part of the other question that @jaromandaX answered.

This is really the same question - how to handle a promise in a promise.

Looking at your code the resizeImage code had a problem because the blob was not really a blob, I think, which was why I had to use blobutil in my other example. This is really a question on how to handle this with a promise - if I needed to go this way.

My old code was:

function resizeImageOld(blob, maxWidth, maxHeight) {
   // Set img src to ObjectURL
   var showImage = document.createElement('img');
   var url = URL.createObjectURL(blob);
   showImage.src = url;

   var newImage;

   showImage.onload = function () {
      URL.revokeObjectURL(showImage.src);
      var canvas = document.createElement("canvas");
      var ctx = canvas.getContext("2d");

      var MAX_WIDTH = maxWidth, maxHeight;
      var MAX_HEIGHT = 60;
      var width = showImage.width;
      var height = showImage.height;

      if (width > height) {
     if (width > MAX_WIDTH) {
        height *= MAX_WIDTH / width;
        width = MAX_WIDTH;
     }
      } else {
     if (height > MAX_HEIGHT) {
        width *= MAX_HEIGHT / height;
        height = MAX_HEIGHT;
     }
      }

      canvas.width = width;
      canvas.height = height;

      ctx.drawImage(showImage, 0, 0, width, height);
      newImage = canvas.toDataURL("image/png");
   }
   return newImage;
}

Making it a promise was a way to get the blob resized and appended to the DOM before it handled another image. This way it never did the onload event.

I changed it to:

function resizeImage(blob, maxWidth, maxHeight) {
   // Set img src to ObjectURL

   return new Promise(function (resolve, reject) {

      var showImage = document.createElement('img');
      var url = URL.createObjectURL(blob);
      var newImage;

      showImage.onload = function () {
     URL.revokeObjectURL(showImage.src);
     var canvas = document.createElement("canvas");
     var ctx = canvas.getContext("2d");

     var MAX_WIDTH = maxWidth, maxHeight;
     var MAX_HEIGHT = 60;
     var width = showImage.width;
     var height = showImage.height;

     if (width > height) {
        if (width > MAX_WIDTH) {
           height *= MAX_WIDTH / width;
           width = MAX_WIDTH;
        }
     } else {
        if (height > MAX_HEIGHT) {
           width *= MAX_HEIGHT / height;
           height = MAX_HEIGHT;
        }
     }

     canvas.width = width;
     canvas.height = height;

     ctx.drawImage(showImage, 0, 0, width, height);
     newImage = canvas.toDataURL("image/png");

     resolve(newImage);
      }

      showImage.src = url;
   })
}

I then changed your code, which now calls this promise to the following and wanted to see if this was the correct way to handle it or would it just jump out of the promise and continue on and come back later after it went through the other images. It does seem better but not sure.

Your code:

function readAllImagesFromPouch(id, imageDisplay) {

   var startElement = document.getElementById(imageDisplay);
   var img = "";
   var imgBlob;
   var base64str;
   var fileName;

   ClearImagePanel();

   // Get all attachments for this id

   return DB_TaskImages.get(id, { attachments: true }).then(function (doc) {

      // a promise to chain to - all following tasks will be performed in series, not parallel - this can be changed

      var promise = Promise.resolve();

      Object.keys(doc._attachments).forEach(function (key) {
     promise = promise.then(function () {
        return DB_TaskImages.getAttachment(doc._id, key).then(function (blob) {

           img = document.createElement('img');

           return resizeImage(blob, "100", "60").then(function(blob) {
          var url = URL.createObjectURL(blob);

          img.src = myUrl;
          $(startElement).append(img.outerHTML);

           })
        }).catch(function (err) {
           alert("No attachment for doc._id: " + doc._id + " and key: " + key);
        })
     });
      });
      return promise;
   }).catch(function (err) {
      console.log("Image not there for id: " + id);
      showMsg("Image not there for id: " + id);
      throw err;
   })
}

Is this right or is it the same type of anti-pattern you mentioned before?

I also had two other questions I couldn't seem to find elsewhere.

  1. On the initial promise of a chain (DB_TaskImages.getAttachment, in this case), do we do a "return" because we are returning to the following "then"? I was not sure, since I had seen examples where there was a return and others where there wasn't a return on the first promise.

  2. In my example, for the "return resizeImage" promise, do I need a .then after it as well as the catch? I noticed that after you did a "return promise", there was no .then but there was a catch.

Just curious.

Thanks.

tshad
  • 335
  • 2
  • 4
  • 18