0

I have an array that has an image id, name and the image itself (base64). In some cases the id will match, so it needs use a different procedure with a revision number for a parameter. But they have to run sequentially, so as to know whether an id already exists or not.

I have a promise that does all this and would need to be called each time, but I can't seem to figure out the best way to do this. Some ideas online are using "reduce" but not sure how that works.

The code is:

    function resetImagesToPouch(image, id, imageName) {

       return new Promise(function (resolve, reject) {

          var rev;
          var imageType;

          // check if attachment already exists

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

         rev = doc._rev;    // Get the revision number since we are updating and adding attachment to existing doc.

         DB_TaskImages.putAttachment(id, imageName, rev, image, "image/jpeg").then(function () {
            console.log("Attachment added successfully");
            return;
         }).then(function () {
            resolve();
         }).catch(function (err) {
            reject(err);
         });
         return;
          }).catch(function (err) {
         if (err.status = '404') {      // if this is a new document - add new attachment
            //imageType = GetImageTypeFromBase64(image);
            DB_TaskImages.putAttachment(id, imageName, image, "image/jpeg").then(function () {
               console.log("Attachment added successfully");
               return;
            }).then(function () {
               resolve();
            }).catch(function (err) {
               reject(err);
            })
         }
          });
       });      // end promise
    }

I have an array of images the I need to refresh my table with such as:

    images = {
            {id = "111", imageName='fighter.jpg', image='aaaaaddddd'},
            {id = "111", imageName='horse.jpg', image='aaasdfssdaaddddd'},
            {id = "112", imageName='cat.jpg', image='aasdfaaadsdfsfdddd'},
            {id = "113", imageName='dog.jpg', image='aaasdfasfaaaddddd'},
            {id = "1234", imageName='apple.jpg', image='aaaasdfasaaddddd'}
         }

I don't know how many will be in the array or what the values would be. How would I do this? I can't use Promise.all (I don't think) as they can't fire of together but the .get and .put are async and I need the each promise to finish. I can't use thenables as I don't know how many there will be.

Thanks

The answer below worked fine except it is part of a chain and exited the code before it finished.

   }).then(function () {
      return ajaxCallForJsonTwoResults(URI_WorkIssues);
   }).then(function (args) {
      args[1].reduce(function (promise, image) {
     return promise.then(function () {
        return resetImagesToPouch(image);
     });
      }, Promise.resolve(args)).then(function () {
     console.log('all done with images');
      });
   }).then(function (args) {
      return callBulkDocLoad(DB_WorkIssues, args[0]);
   }).then(function () {

In the code, I am passing args (which has two arrays in it - args[0] and args[1]). I am handling two databases here so I needed two responses. The problem is that when I come back from my ajax call, I use args[1] for the first promise and args[0] for the other. But since I come back from 1st promise prematurely, and haven't hit the resolve yet which passes args to the callBulkDocLoad function, it is undefined.

Did I miss something?

Thanks.

It almost works.

The return did keep the loop finishing up the reduce loop before jumping out but it didn't do the resetImagesToPouch call until after all the loops were done and only once.

      var argsSaved;
          ...
          // WorkIssues
   }).then(function () {
      if (DB_WorkIssues != null)
     return DB_WorkIssues.erase();
   }).then(function () {
      return DB_WorkIssues = new PouchDB(DBNAME_WorkIssues, { auto_compaction: true });
   }).then(function () {
      return ajaxCallForJsonTwoResults(URI_WorkIssues);
   }).then(function (args) {
      argsSaved = args;
      return args[1].reduce(function (promise, image) {
     return promise.then(function () {
        return resetImagesToPouch(image);
     });
      }, Promise.resolve(args)).then(function () {
     console.log('all done with images');
      });
   }).then(function (args) {
      return callBulkDocLoad(DB_WorkIssues, argsSaved[0]);
   }).then(function () {
      updateWorkIssuesJson();
      loadTaskList(false);

Here is the resetImagesToPouch function:

    var resetImagesToPouch = function resetImagesToPouch(_ref) {
       var image = _ref.AttachmentFile,
           id = _ref.JobNumber,
           imageName = _ref.DocTitle;
       return DB_TaskImages.get(id, {
          attachments: true
       }).then(function (doc) {
          return DB_TaskImages.putAttachment(id, imageName, doc._rev, image, 'image/jpeg');
       }).catch(function (err) {
          if (err.status = '404') {
         // if this is a new document - add new attachment
         //var imageType = GetImageTypeFromBase64(image);
         return DB_TaskImages.putAttachment(id, imageName, image, 'image/jpeg');
          } else {
         throw err;
          }
       });
    };

Before the return, the loop only happened once then jumped out. There are six images and the loop now happens times but doesn't do the resetImagesToPouch until all the loops are done.

So it goes like this: It does the reduce first, then it does "return promise.then" and Promise.Resolve six times, then it jumps to the "then" then it jumps back once to the resetImagesToPouch where it does the DB_TaskImages.get to see if this is the first image for this id and since the database is empty, it goes to the catch and inside the 404 if statment and then the DB_TaskImages.putAttachment where it where it jumps to the end of the function and then to the catch at the end of the chain with the error:

err = r {status: 404, name: "not_found", message: "missing", error: true, reason: "deleted"}

That was the error we got from the .get call that gave us the 404 (I believe), so I don't know why it went there. This doesn't seem to be the error you would get from a put. So it never does the last part of the chain, since it jumped over it and there was no image record put in the table.

Thanks.

After it was pointed out that I was missing a "==", I changed resetImagesToPouch() and fixed that as well as added thens and catches to the puts. This seemed to fix the problem of not finishing the chain.

    var resetImagesToPouch = function resetImagesToPouch(_ref) {
       var image = _ref.AttachmentFile,
           id = _ref.JobNumber,
           imageName = _ref.DocTitle;
       return DB_TaskImages.get(id, {
          attachments: true
       }).then(function (doc) {
          DB_TaskImages.putAttachment(id, imageName, doc._rev, image, 'image/jpeg').then(function () {
         console.log("Success insert of multiple attachment");
         return;
          }).catch(function () {
         console.log("Error in resetImagesToPouch: " + err);
         showMsg("Error in resetImagesToPouch: " + err);
          })
       }).catch(function (err) {
          if (err.status == '404') {
         // if this is a new document - add new attachment
         //var imageType = GetImageTypeFromBase64(image);
         DB_TaskImages.putAttachment(id, imageName, image, 'image/jpeg').then(function () {
            console.log("Successfull insert of multiple attachment");
            return;
         }).catch(function () {
            console.log("Error in resetImagesToPouch: " + err);
            showMsg("Error in resetImagesToPouch: " + err);
         });
          } else {
         console.log("At throw error");
         throw err;
          }
       });
    };

But it still does not insert any images. The parameters look fine but it always takes the catch with the above 404 error with the "not found" and "deleted". The only thing I can think (but not sure why this would be the case) is that I do an erase on this database (as I do for all the databases) before I do these sections.

          // TaskImages
       }).then(function () {
          if (DB_TaskImages != null)
         return DB_TaskImages.erase();
       }).then(function () {
          return DB_TaskImages = new PouchDB(DBNAME_TaskImages, { auto_compaction: true });

I do this for about 6 databases because I am reloading them from the server. I wondered if maybe the .gets and .puts in the loop may be causing the issue? Doesn't seem to make much sense.

I just tried something new with the erase and changed it to destroy. This works better. Unlike the erase, which never adds any of the images, this one will add 4 of the 6 images. It gets a 404 error on two of them but instead of saying reason: "deleted", it says reason: "missing". I think the issue with this one is that these are actually multiple attachments for the same ID. But for some reason when the .get was done, it didn't find the record, so it did a .put without a revision (took the 404 route) and found the record at this point and gave the error because the revision was missing. Perhaps, when it first record it hadn't finished when the 2nd record came through.

Thanks.

I redid the resetImagesToPouch earlier today because I just couldn't get the old version to work. It always almost works but there was always something that didn't and I was running out of time. The 404 errors caused by the erase and the missing images were an issue.

I ended up going back to my resetImagesToPouch being a promise and was finally able to get it to work without jumping and for some reason had no issues with the images, as long as I did a destroy and not an erase. An erase would not save anything. It just came back with 404-missing or 404-deleted. I just ran out of time trying to figure out why.

Here is what I finally did this afternoon that worked all the way around.

          // TaskImages
       }).then(function () {
          if (DB_TaskImages != null)
         return DB_TaskImages.destroy();
       }).then(function () {
          return DB_TaskImages = new PouchDB(DBNAME_TaskImages, { auto_compaction: true });

          // WorkIssues
       }).then(function () {
          if (DB_WorkIssues != null)
         return DB_WorkIssues.erase();
       }).then(function () {
          return DB_WorkIssues = new PouchDB(DBNAME_WorkIssues, { auto_compaction: true });
       }).then(function () {
          return ajaxCallForJsonTwoResults(URI_WorkIssues);
       }).then(function (args) {
          argsSaved = args;
          console.log("Before the reduce");
          return args[1].reduce(function (promise, image) {
         console.log("After the reduce and before the promise.then");
         return promise.then(function () {
            console.log("Inside the promise.then and before the resetImagesToPouch");
            return resetImagesToPouch(image).then(function () {
               console.log('Image written to Pouch successfully');
            }).catch(function (err) {
               console.log("Error in resetImagesToPouch with err: " + err);
            });
            console.log("After the resetImagesToPouch");
         });
         console.log("Before the Promise.resolve");
          }, Promise.resolve(args)).then(function () {
         console.log('all done with images');
          });
       }).then(function (args) {
          console.log("Before the callBulkDocLoad");
          if (argsSaved[2].length > 0) {
         console.log("Ajax issue with err: " + argsSaved[2][0]);
         msg = "Ajax issue with err: " + argsSaved[2][0];
          }
          return callBulkDocLoad(DB_WorkIssues, argsSaved[0]);
       }).then(function () {
          console.log("Before the updateWorkIssuesJson");
          updateWorkIssuesJson();
          loadTaskList(false);

And the resetImagesToPouch:

    function resetImagesToPouch(_ref) {
       var image = _ref.AttachmentFile,
        id = _ref.JobNumber,
        imageName = _ref.DocTitle;

       return new Promise(function (resolve, reject) {

          var rev;
          var imageType;

          // check if attachment already exists

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

         rev = doc._rev;    // Get the revision number since we are updating and adding attachment to existing doc.

         DB_TaskImages.putAttachment(id, imageName, rev, image, "image/jpeg").then(function () {
            console.log("Attachment added successfully");
            return;
         }).then(function () {
            resolve();
         }).catch(function (err) {
            reject(err);
         });
         return;
          }).catch(function (err) {
         if (err.status = '404') {      // if this is a new document - add new attachment
            //imageType = GetImageTypeFromBase64(image);
            DB_TaskImages.putAttachment(id, imageName, image, "image/jpeg").then(function () {
               console.log("Attachment added successfully");
               return;
            }).then(function () {
               resolve();
            }).catch(function (err) {
               reject(err);
            })
         }
          });
       });      // end promise
    }

It may not be the best way to handle it but it is working. I do understand it better now but still need to work with it for the pieces I don't. Such as the part inside the reduce, I don't really understand all that yet and will look at it when I have more time. I do get some of it. But not sure why it bounces back and forth between the promise.then and the Promise.resolve for all the images before it does all the resetImagesToPouch calls. That will give me something to work on when I am out of town for Easter.

Thanks for all the help.

I doubt I would have got it working out without it.

tshad
  • 335
  • 2
  • 4
  • 18
  • you'll want to chain the promises somehow - and as `DB_TaskImages.get` already returns a promise stop using the Promise constructor anti-pattern – Jaromanda X Apr 10 '17 at 09:47
  • 1
    You are using the [Promise constructor antipattern](http://stackoverflow.com/questions/23803743/what-is-the-explicit-promise-construction-antipattern-and-how-do-i-avoid-it). – trincot Apr 10 '17 at 09:50
  • you "images" object is invalid syntax, by the way – Jaromanda X Apr 10 '17 at 09:57
  • @JaromandaX: You're right the images syntax isn't correct, just illustration of the array. It is just an array (coming from c#) as an array that has the ID, name and the image itself. – tshad Apr 10 '17 at 10:20
  • @JaromandaX: don't I need the promise if resetImagesToPouch is going to be called over and over? – tshad Apr 10 '17 at 10:22
  • @tshad - no, each call to `resetImagesToPouch` returns a Promise for each image – Jaromanda X Apr 10 '17 at 10:26
  • Two things in `resetImagesToPouch()` (1) you want to catch a 404 arising from `DB_TaskImages.get()` not from the first `DB_TaskImages.putAttachment()`, therefore the basic pattern should be `return DB_TaskImages.get(...).then(successFn, errorFn);` not `return DB_TaskImages.get(...).then(successFn).catch(errorFn);` (2) `if (err.status = '404')` should read `if (err.status == '404')` – Roamer-1888 Apr 11 '17 at 07:24
  • Now you have reverted to a version of `resetImagesToPouch()` with missing returns. They are important! – Roamer-1888 Apr 11 '17 at 20:16
  • Also, if you insert a `.catch(...)` for debugging, don't forget to rethrow the error. Otherwise, `.catch()` *does what it says on the tin* - it catches! – Roamer-1888 Apr 11 '17 at 20:19
  • @Roamer-1888: I realized that this morning and redid it as you can see above. Probably still not right but it seems to do the job. Thanks. – tshad Apr 12 '17 at 06:01
  • @tshad, if you don't take advice, you won't learn. Jaromanda X showed you how purge the Promise constructor antipattern and how to return promises within a promise chain. By basing your latest version on your original, all that advice has gone straight down the plug-hole. – Roamer-1888 Apr 12 '17 at 07:17
  • @Roamer-1888, Not true. I liked what Jaromanda did, but I couldn't get it to work with the images and I have a deadline with my customer. I would have loved to spend more time on this (and I will) but I have a boss and client that need this done. His code was working except for handling the images. I can't figure out why mine worked and his didn't - it should have. His couldn't seem to deal with multiple attachments for the same ID. The .get was not seeing an ID that I know was there. – tshad Apr 12 '17 at 10:00

1 Answers1

1

Using array.reduce is simple, once you have a valid array of course

let images = [
    { id: '111', imageName: 'fighter.jpg', image: 'aaaaaddddd' },
    { id: '111', imageName: 'horse.jpg', image: 'aaasdfssdaaddddd' },
    { id: '112', imageName: 'cat.jpg', image: 'aasdfaaadsdfsfdddd' },
    { id: '113', imageName: 'dog.jpg', image: 'aaasdfasfaaaddddd' },
    { id: '1234', imageName: 'apple.jpg', image: 'aaaasdfasaaddddd' }
]

rewrite the resetImagesToPouch to correctly return a promise

let resetImagesToPouch = ({image, id, imageName}) => 
    DB_TaskImages.get(id, {
        attachments: true
    })
    .then((doc) => DB_TaskImages.putAttachment(id, imageName, doc._rev, image, 'image/jpeg'))
    .catch ((err) => {
        if (err.status = '404') { // if this is a new document - add new attachment
            //var imageType = GetImageTypeFromBase64(image);
            return DB_TaskImages.putAttachment(id, imageName, image, 'image/jpeg');
        } else {
            throw err;
        }
    });

Now, you let reduce do it's magic

images.reduce((promise, image) => promise.then(() => resetImagesToPouch(image)), Promise.resolve())
.then(() => {
    console.log('all done');
});

if ES2015+ scares you

var resetImagesToPouch = function resetImagesToPouch(_ref) {
    var image = _ref.image,
        id = _ref.id,
        imageName = _ref.imageName;
    return DB_TaskImages.get(id, {
        attachments: true
    }).then(function (doc) {
        return DB_TaskImages.putAttachment(id, imageName, doc._rev, image, 'image/jpeg');
    }).catch(function (err) {
        if (err.status = '404') {
            // if this is a new document - add new attachment
            //var imageType = GetImageTypeFromBase64(image);
            return DB_TaskImages.putAttachment(id, imageName, image, 'image/jpeg');
        } else {
            throw err;
        }
    });
};

Now, you let reduce do it's magic

images.reduce(function (promise, image) {
    return promise.then(function () {
        return resetImagesToPouch(image);
    });
}, Promise.resolve()).then(function () {
    console.log('all done');
});

Looking at the "chain" in the edited question

}).then(function () {
    return ajaxCallForJsonTwoResults(URI_WorkIssues);
}).then(function (args) {
    // add return here
    return args[1].reduce(function (promise, image) {
        return promise.then(function () {
            return resetImagesToPouch(image);
        });
    }, Promise.resolve(args))
    .then(function () {
        console.log('all done with images');
        // pass on args down the chain
        return args;
    });
}).then(function (args) {
    return callBulkDocLoad(DB_WorkIssues, args[0]);
}).then(function () {

and for the sake of completeness - in ES2015+ that would be

})
.then(() => ajaxCallForJsonTwoResults(URI_WorkIssues))
.then(([docs, images]) => 
    images
    .reduce((promise, image) => 
        promise.then(() => 
            resetImagesToPouch(image)),
        Promise.resolve()
    )
    .then(() => docs)
)
.then(docs => callBulkDocLoad(DB_WorkIssues, docs))
.then(() => {
Jaromanda X
  • 53,868
  • 5
  • 73
  • 87
  • I think I prefer your 2nd example. I am having enough trouble getting my head around it as is. Thanks,. – tshad Apr 10 '17 at 10:27
  • I think I missed something or maybe because you didn't know I was using this in a chain, the code continues on in the chain before the image code is done. I don't have a problem with the other code running at the same time, but other list that I needed passed doesn't get passed. I might be able to put it in a local variable before executing the image code but wanted to know if there was something I could do that would have it wait until done. I added the chained code above. – tshad Apr 10 '17 at 11:19
  • where in the chain did you put the code? nevermind - looking at it now – Jaromanda X Apr 10 '17 at 11:40
  • Perhaps you need to `return args[1].reduce ...` - to return the last promise in the reduce chain - however `.then(function (args) { return callBulkDocLoad(DB_WorkIssues, args[0]); })` wont have `args` you expect – Jaromanda X Apr 10 '17 at 11:42
  • @tshad - edited the answer with the chain from the now edited question – Jaromanda X Apr 10 '17 at 11:46
  • It almost worked but not quite. I added more to the question. I resolved the args issue by putting another local variable at the top of the function (argsSaved) and move args to it before I did anything and solved that problem. – tshad Apr 11 '17 at 04:56
  • really? perhaps you used the code I posted incorrectly, because `args` is passed on after the `reduce` - seeing as you don't need the result of the reduce in your code – Jaromanda X Apr 11 '17 at 04:58
  • That's possible. But as I explain above in my question, it never gets to the thenable section. – tshad Apr 11 '17 at 06:23