Promise with Pouch not returning from call to insi

2019-09-15 13:28发布

问题:

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

回答1:

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


回答2:

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.