0

The return Promise.all([photoArray]) returns an empty array, seemingly not waiting for the callFB to return its promise that then pushes into the array.

I am not sure what I am doing wrong but am relatively new to Promises with for loops and Ifs.

I am not sure exactly if I am using the correct number of Promises but I seem to not be able to get the 3rd tier Promise.all to wait for the for loop to actually finish (in this scenario, the for loop has to look through many item so this is causing an issue where it is not triggering callFeedback for all the items it should before context.done() gets called.

I have tried using Q.all also for the Promise.all([photoArray]) but have been unable to get that working.

module.exports = function (context, myBlob) {
                var res = myBlob
                var promiseResolved = checkPhoto(res,context);
                var promiseResolved2 = checkVideo(res,context); 


            Promise.all([promiseResolved, promiseResolved2]).then(function(results){
            context.log(results[0], results[1]);
 //           context.done();
            });


        });  
    };
};

function checkPhoto(res, context){
    return new Promise((resolve, reject) => {
    if (res.photos.length > 0) {
        var photoArray = [];
        for (var j = 0; j < res.photos.length; j++) {
            if (res.photos[j].feedbackId !== null){
                var feedbackId = res.photos[j].feedbackId;
                var callFB = callFeedback(context, feedbackId);                    
                Promise.all([callFB]).then(function(results){
                    photoArray.push(results[0]);
                 });        
            } else {    
                photoArray.push("Photo " + j + " has no feedback");
            }

        }

        return Promise.all([photoArray]).then(function(results){
            context.log("end results: " + results);
            resolve(photoArray);
        });
    } else {
        resolve('No photos');
    }

})
}
function checkVideo(res, context){
        return new Promise((resolve, reject) => {
            same as checkPhoto
    })
    }
function callFeedback(context, feedbackId) {
        return new Promise((resolve, reject) => {

        var requestUrl = url.parse( URL );

            var requestBody = {
                "id": feedbackId
            };

            // send message to httptrigger to message bot
            var body = JSON.stringify( requestBody );

            const requestOptions = {
            standard
            };

            var request = https.request(requestOptions, function(res) {
                var data ="";
                res.on('data', function (chunk) {
                    data += chunk
    //                context.log('Data: ' + data)
                });
                res.on('end', function () {
                resolve("callFeedback: " + true);
                })
            }).on('error', function(error) {
            });
            request.write(body);
            request.end();
            })

}
JDT
  • 965
  • 2
  • 8
  • 20
  • You are correct, a for loop will not wait for any async operations to finish before executing the next step. By the time `Promise.all([photoArray])` is called, `photoArray` is most likely empty. – Luiz Chagas Jr Jul 31 '18 at 21:22

1 Answers1

0

The code suffers from promise construction antipattern. If there's already a promise (Promise.all(...)), there is never a need to create a new one.

Wrong behaviour is caused by that Promise.all(...).then(...) promise isn't chained. Errors aren't handled and photoArray.push(results[0]) causes race conditions because it is evaluated later than Promise.all([photoArray])....

In case things should be processed in parallel:

function checkPhoto(res, context){
    if (res.photos.length > 0) {
        var photoArray = [];
        for (var j = 0; j < res.photos.length; j++) {
            if (res.photos[j].feedbackId !== null){
                var feedbackId = res.photos[j].feedbackId;
                var callFB = callFeedback(context, feedbackId);
                // likely no need to wait for callFB result
                // and no need for Promise.all
                photoArray.push(callFB);
            } else {    
                photoArray.push("Photo " + j + " has no feedback");
            }
        }

        return Promise.all(photoArray); // not [photoArray]
    } else {
        return 'No photos';
    };
}

callFB promises don't depend on each other and thus can safely be resolved concurrently. This allows to process requests faster.

Promise.all serves a good purpose only if it's used to resolve promises in parallel, while the original code tried to resolve the results (results[0]).

In case things should be processed in series the function benefits from async..await:

async function checkPhoto(res, context){
    if (res.photos.length > 0) {
        var photoArray = [];
        for (var j = 0; j < res.photos.length; j++) {
            if (res.photos[j].feedbackId !== null){
                var feedbackId = res.photos[j].feedbackId;
                const callFBResult = await callFeedback(context, feedbackId);
                // no need for Promise.all
                photoArray.push(callFBResult);
            } else {    
                photoArray.push("Photo " + j + " has no feedback");
            }
        }

        return photoArray; // no need for Promise.all, the array contains results
    } else {
        return 'No photos';
    };
}

Add try..catch to taste.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • Thanks for the reply - why would I not have to wait for callFB result? – JDT Jul 31 '18 at 21:29
  • I added a clarification. Because this defies the purpose of Promise.all. It's supposed to process promises of photos (I guess that's what callFB is) in parallel. And waiting for each result processes them in series and makes the loop slower. – Estus Flask Jul 31 '18 at 21:31
  • @AnonymousDownvoter Downvoting both the question and existing answers in batch without any feedback is always helpful. Thank you for making SO a better place. – Estus Flask Jul 31 '18 at 21:39
  • I am a bit confused still - I am happy for the promises in the for loop iterations to run in parallel, I just don't want it to trigger the Promise.all([photoArray]) until each individual loop has finished (and by finished, it has callbacked from callFB and pushed to the array) - is this prevented because of the construction antipattern? – JDT Jul 31 '18 at 21:41
  • *to run in parallel* - they run in parallel out of control. You get race condition. *I just don't want it to trigger the Promise.all([photoArray]) until each individual loop has finished* - you would need async/await, as I originally suggested to make this easily. But you don't need that because these promises don't depend on each other and thus can be processed in parallel. Why would you use Promise.all then? It doesn't make sense if you wait for all promises in the loop to be resolved. – Estus Flask Jul 31 '18 at 21:46
  • the reason I used the Promise.all[photoArray], was to make sure checkPhoto resolves after all the loops are done. I have implemented your first section of code and it works, so thanks a lot, this has taken me quite a while to try and figure out! I have harder things to solve with this same issue so hopefully will be able to carry this over. I have thought of async/await but I am using Azure Functions and would have to port all my code to a new App Function and enter the beta otherwise async is not supported. Lastly - why would I remove the "return new promise" from checkPhoto? – JDT Jul 31 '18 at 21:58
  • But this piece of code `Promise.all([callFB]).then(function(results){ photoArray.push(results[0]); });` was supposed to fill `photoArray` with *results*, not *promises*. There was nothing to resolve with `Promise.all(photoArray)`. Yes, the first snippet in the answer is the way it's usually done, glad it works for you. – Estus Flask Jul 31 '18 at 22:04
  • Can you clarify why photoArray not [photoArray] changes output from working to promise ? – JDT Jul 31 '18 at 22:07
  • What do you mean? `Promise.all([photoArray])` is incorrect because `Promise.all` expects array of promises. `photoArray` is already an array, and `[photoArray]` makes it an array of arrays. – Estus Flask Jul 31 '18 at 22:30
  • I get that - it was just that photoArray resolves correctly as expected but [photoArray] resolved as "promise " – JDT Jul 31 '18 at 22:39