0

I need to perform a rather complicated chain of promise resolutions to get and save data that is located in PDFs that are uploaded by a user. Everything works fine for single PDFs, but it breaks as soon as I try to upload multiple PDFs because the order in which the resolution proceeds isn't as I expect.

I call the function saveDoc on each file in an array:

     saveDoc: function(){
         var files = this.$refs.upload.uploadFiles
         var self=this;
         var promises = []
         for (var i=0; i<files.length; i++){
             (function(){
                 var file = files[i]
                 var name = file['name']
                 if (!(/\.pdf/i.test(name))){
                     name+='.pdf'
                 }
                 var type = mime.lookup(name)
                 var file = file['raw'];
                 var beforeUrl = self.selectedCategories.join('&&&')
                 console.log('going to save: ' + name)   // Because of the IIFE, I'm not expecting this to log only after all steps 1 - 20 have been completed for each file
                 Store.getFileData(file).then(function(data){
                     console.log(2)
                     return Store.saveDoc(name, type, data, beforeUrl).then(url => {
                         console.log(8)
                         return Text.getText(url).then(text => {
                             console.log(12)
                             return Text.getMetadata(text, url).then(metadata => {
                                 console.log(20)
                                 if (metadata.length){


                                     return Store.saveMetadata(beforeUrl, metadata, name)
                                 }
                                 return Store.createCategory(name, self.selectedCategories, '')
                             })
                         })
                     })
                 })
             })()
         }
     },

I'm sure the promises could use some work, but what seems to be the problem is that the line console.log('going to save: ' + name) is called twice before the entire sequence 1-20 is carried out for one file (see error message of number sequence at bottom of this post). I tried to prevent this using an IIFE, but I guess I didn't do this right.

store.js

    getData: function(url){
        return new Promise(function(accept, reject){
            console.log(4)
            documentation.get(url, function(err, body) {
                console.log(5)
                if (!err){
                    console.log(body);
                    accept(body)
                }
            });
        })
    },
    getFileData: function(file){

        var reader = new FileReader();
        return new Promise(function(accept, reject){
            console.log(1)
            reader.onload = (e) => {
//              var data = e.target.result.replace(/^data:[A-Za-z]+\/[A-Za-z]+;base64,/, '')
                //              console.log('base 64: ' + data)

                var res = new Uint8Array(e.target.result)
                accept(res)
            };
            reader.readAsArrayBuffer(file);
        })
    },
    saveDoc: function(name, type, filedata, url){
        console.log(3)
        var self=this
        return new Promise(function(accept, reject){
            return self.getData(url).then(data => {
                console.log(6)
                var rev = data['_rev']
                return documentation.attachment.insert(url, name, filedata, type,
                                                { rev: rev }, function(err, body) {
                                                    if (!err){
                                                        console.log(7)
                                                        var fullUrl = 'http://dev04/documentation/'+url+'/'+name

                                                        accept(fullUrl)
                                                    }
                                                    else {
                                                        console.log(err)
                                                    }
                                                })
            }).catch(err => {
                console.log(err)
            })
        })
    },
    saveMetadata: function(url, metadata, name){
        var fileName = path.basename(name)

        var self=this
        return new Promise(function(accept, reject){
            self.getData(url).then(data => {
                var meta
                var rev = data['_rev']
                if(!data['metadata']){
                    data['metadata'] = {}
                }
                data['metadata'][fileName] = metadata

                var datastring = JSON.stringify(data)

                documentation.insert(data, url, function(err, body, header) {
                    if (err) {
                        console.log(err.message);
                        return;
                    }

                });

            }).catch(err => {
                console.log(err)
            })
        })
    },

text.js

export default {
    getText: function(url){
        console.log(9)
        var result = []
        return new Promise(function(accept, reject){
            console.log(10)
            return pdfjs.getDocument(url).then(pdf => {
                console.log(11)
                var pdf = pdfjs.getDocument(url);
                return pdf.then(function(pdf) { // get all pages text
                    var maxPages = pdf.pdfInfo.numPages;
                    var countPromises = []; // collecting all page promises
                    for (var j = 1; j <= maxPages; j++) {
                        var page = pdf.getPage(j);
                        var txt = "";
                        countPromises.push(page.then(function(page) { // add page promise
                            var textContent = page.getTextContent();
                            //                      console.log('the content is ' + textContent)
                            return textContent.then(function(text){ // return content promise
                                var val = text.items.map(function (s) { return s.str; }).join('&&&')+'&&&'; // value page text
                                result.push(val)
                                //                              console.log(val + ' should be one page of text')
                                return val
                            });
                        }));
                    }
                    // Wait for all pages and join text
                    return Promise.all(countPromises).then(function (texts) {
                        accept(texts.join(''))
                    });
                });
            });
        })
    },
    getMetadata: function(text, url){
        console.log(13)
        var result = []
        var self = this
        return new Promise(function(accept, reject){

            self.getpageno(url).then(function(pagecount){

                console.log(19)
                try {
                    var dataMatch = rx.exec(text)
                    var produktDaten = dataMatch[1].split("&&&").filter(Boolean);
                    console.log(produktDaten)
                    var dokuArr = dataMatch[2].split("&&&").filter(Boolean);
                    for (var i=0; i<produktDaten.length; i+=4){

                        var entry = {}
                        entry.pagecount = pagecount
                        entry.kks = {}
                        entry.kks.pages = {}
                        var kksNummer = produktDaten[i];
                        entry.kks.nummer = kksNummer;
                        for(var j=0; j<dokuArr.length; j+=3){
                            var nummer = dokuArr[j];
                            var beschreibung = dokuArr[j+1];
                            var seite = dokuArr[j+2];
                            // make sure seite is a digit
                            if (!(/^\d+$/.test(seite))){
                                console.log(seite + ' was not a valid page number')
                                throw err
                            }
                            if (/(A|a)lle?/i.test(nummer)){
                                entry.kks.pages[beschreibung] = seite;
                                //                  self.tableEntry.kks.url = url;
                                //                  self.tableEntry.fileName = name;
                                /////               kksNummern.forEach(function(kks){
                                //                      self.tableEntry.kks;
                                //                  })
                            }
                            else if (nummer === kksNummer) {
                                entry.kks.pages[beschreibung] = seite;
                                //                  entry.kks.url = url;
                                //                  entry.fileName = name
                            }
                        }
                        entry.hersteller = produktDaten[i+1]
                        entry.typ = produktDaten[i+2]
                        entry.artikelNummer = produktDaten[i+3]
                        result.push(entry)
                    }
                }
                catch(e){
                    return accept(result)
                }
                return accept(result)
                /*              if (result.length>0){
                    console.log('accepting the result')

                }
                reject()*/

            }).catch(err => {
                console.log(err)
            })
                })
    },
    getpageno: function(url){
        console.log(14)
        var self=this
        var pdf = pdfjs.getDocument(url);
        return pdf.then(function(pdf){
            console.log(15)

            var maxPages = pdf.pdfInfo.numPages;
            var countPromises = []; // collecting all page promises
            for (var j = 1; j <= maxPages; j++) {
                try {
                    var page = pdf.getPage(j);
                    var txt = "";
                    countPromises.push(page.then(function(page) { // add page promise
                        var textContent = page.getTextContent();
                        return textContent.then(function(text){ // return content promise
                            console.log(16)
                            return text.items.map(function (s) { return s.str; }).join('&&&'); // value page text
                        });
                    }));
                }
                catch(e){
                    console.log(e)
                }
            }
            // Wait for all pages and join text
            return Promise.all(countPromises).then(function (texts) {
                // since doumentation pages do not add the final '&&&', must add one manually (only after rx has been found)
                console.log(17)
                var fulltext = texts.reduce(function(full, text){
                    if (rx.test(full)){
                        var next = '&&&'+text
                        return full+=next
                    }
                    return full+=text
                }, '')
                return [fulltext, texts]
            });
        }).then(function(textarr){
            console.log(18)
            var fulltext = textarr[0]
            self.fulltext = fulltext;
            var texts = textarr[1]
            try {
                var partialmatch = rx.exec(fulltext)[0]
                var count = texts.reduce(function(pageno, text){
                    var tomatch = text.replace(/.*Typ&&&/, '')
                    if (tomatch.length>0 && partialmatch.indexOf(tomatch) > -1){
                        pageno++
                    }
                    return pageno;
                }, 0)
            }
            catch(e){
                console.log(e)
            }
            return count;
        }).catch(err => {console.log(err)})
    }
}

I use the console to log the order I am expecting for the functions. I am expecting the numbers 1 - 20 in order, but I get the following.

    going to save: 03_.pdf  selector.js:211:6
1  store.js:227:4
going to save: 2017.07.05_0016 E161206.pdf  selector.js:211:6
1  store.js:227:4
2  selector.js:213:7
3  store.js:239:3
4  store.js:213:4
2  selector.js:213:7
3  store.js:239:3
4  store.js:213:4
5  store.js:215:5
Object { _id: "Test", _rev: "36-85a08c0852ccab78c0b4c10369e83fb2", rank: 7, icon: "wrench", metadata: Object, _attachments: Object }  store.js:217:6
6  store.js:243:5
5  store.js:215:5
Object { _id: "Test", _rev: "36-85a08c0852ccab78c0b4c10369e83fb2", rank: 7, icon: "wrench", metadata: Object, _attachments: Object }  store.js:217:6
6  store.js:243:5
7  store.js:247:7
8  selector.js:215:8
9  text.js:12:3
10
11  text.js:17:5
12  selector.js:217:9
13  text.js:50:3
14  text.js:112:3
15  text.js:116:4
16 text.js:129:8
17  text.js:142:5
18  text.js:153:4
19  text.js:57:5
20

Can anyone offer any advice on how to get this order correct? Thank you.

David J.
  • 1,753
  • 13
  • 47
  • 96
  • Avoid the [`Promise` constructor antipattern](https://stackoverflow.com/q/23803743/1048572?What-is-the-promise-construction-antipattern-and-how-to-avoid-it)! – Bergi Feb 26 '18 at 13:38
  • And exactly *why* would you expect the logs in sequential order? There is nothing in your loop that makes it wait for the promises from the previous iteration. In fact, nothing waits for your promise chain at all, not even an error handler. – Bergi Feb 26 '18 at 13:40
  • Also, what's wrong with processing the uploaded PDFs concurrently? There doesn't seem to be any dependencies between them. – Bergi Feb 26 '18 at 13:41
  • @Bergi The dependencies between the PDFs are due to them needing to be saved along with metadata about them into CouchDB. Once one has been saved it returns a _rev # that needs to be used in order to save the next. – David J. Feb 26 '18 at 13:45
  • @Bergi I guess I had a misconception about IIEFs in for loops that I'm trying to correct... – David J. Feb 26 '18 at 13:47
  • Shouldn't you then explicitly pass around that `_rev` from one to the next? – Bergi Feb 26 '18 at 14:03

0 Answers0