简体   繁体   中英

Controlling flow with nested promises in loop

I'm having trouble controlling the flow of my method using promises:

//FIND CHECKED OUT FILES
getCheckedOutFiles = function () {
    console.log('Get checked out files');
    var d = $q.defer();

    // Store final results and pass to then callback
    var checkedOutFiles = window.x = [];

    // Promise, returns collection with all sites 
    SiteService.getAllSites()
        .then(sites => {
            // For each site get lists then get items matching filter
            sites.forEach(function (site) {
                var web = new $pnp.Web(site.url);

                return web.lists
                    .filter("BaseTemplate eq 101")
                    .get() // Returns promise with all lists matching filter 
                    .then(lists => {
                        // Go through each list and find matching item 
                        lists.forEach(function (list) {
                            web.lists.getByTitle(list.Title).items
                                .filter("CheckoutUser ne null")
                                .expand("File, File/Author, File/ModifiedBy, File/CheckedOutByUser")
                                .get() // Returns promise with all items matching filter 
                                .then(files => {
                                    // Loop through each item, get properties, add to collection 
                                    files.forEach(function (f) {
                                        var fileObject = {
                                            fileName: f.File.Name,
                                            fileUrl: f.File.ServerRelativeUrl,
                                            absoluteUrl: f.File.ServerRelativeUrl,
                                            checkedTo: f.File.CheckedOutByUser.Title,
                                            modified: f.Modified,
                                            modifiedBy: f.File.ModifiedBy.Title,
                                            createdBy: f.File.Author.Title,
                                            created: f.Created,
                                            version: f.File.UIVersionLabel
                                        };
                                        // Add file to collection 
                                        checkedOutFiles.push(fileObject);
                                    }, this);
                                })
                                .catch(e => {
                                    console.log(e);
                                });
                        });
                        // "Ideally" - When all files retrieved return collection of results 
                        d.resolve(checkedOutFiles);
                        return null;
                    })
                    .catch(e => {
                        console.log(e);
                    });
            }, this);
            return null;
        });
    return d.promise;
};

// Returns promise with all checkedout file 
getCheckedOutFiles().then(files => {
    console.log("RESULTS", files);
    d.resolve(files);
});

I'm noticing the console.log("RESULTS", files); will print out before the calls are completed. After the calls are done, window.x will contain the expected data.

Use Promise.all() to wait for all the promises created and then resolve the parent promise

like this (not tested)

//FIND CHECKED OUT FILES
getCheckedOutFiles = function () {
    console.log('Get checked out files');
    var d = $q.defer();

    // Store final results and pass to then callback
    var checkedOutFiles = window.x = [];

    // Promise, returns collection with all sites 
    SiteService.getAllSites()
        .then(sites => {
            // For each site get lists then get items matching filter
            sites.forEach(function (site) {
                var web = new $pnp.Web(site.url);

                return web.lists
                    .filter("BaseTemplate eq 101")
                    .get() // Returns promise with all lists matching filter 
                    .then(lists => {
                        let promises = []
                        // Go through each list and find matching item 
                        lists.forEach(function (list) {

                            let prom = web.lists.getByTitle(list.Title).items
                                .filter("CheckoutUser ne null")
                                .expand("File, File/Author, File/ModifiedBy, File/CheckedOutByUser")
                                .get() // Returns promise with all items matching filter 
                                .then(files => {
                                    // Loop through each item, get properties, add to collection 
                                    files.forEach(function (f) {
                                        var fileObject = {
                                            fileName: f.File.Name,
                                            fileUrl: f.File.ServerRelativeUrl,
                                            absoluteUrl: f.File.ServerRelativeUrl,
                                            checkedTo: f.File.CheckedOutByUser.Title,
                                            modified: f.Modified,
                                            modifiedBy: f.File.ModifiedBy.Title,
                                            createdBy: f.File.Author.Title,
                                            created: f.Created,
                                            version: f.File.UIVersionLabel
                                        };
                                        // Add file to collection 
                                        checkedOutFiles.push(fileObject);
                                    }, this);
                                })
                                .catch(e => {
                                    console.log(e);
                                });
                            promises.push(prom)
                        });
                        // "Ideally" - When all files retrieved return collection of results 
                        Promise.all(promises).then(function(){
                            d.resolve(checkedOutFiles);
                        })
                        return null;
                    })
                    .catch(e => {
                        console.log(e);
                    });
            }, this);
            return null;
        });
    return d.promise;
};

// Returns promise with all checkedout file 
getCheckedOutFiles().then(files => {
    console.log("RESULTS", files);
    d.resolve(files);
});

These are the things that need fixing:

  1. You have two separate loops where you have promises to manage. You need to collect those promises in an array and then use Promise.all() on them to know when all the promises from the loop are done.

  2. You are using an anti-pattern by creating the d promise and then resolving it manually. Instead, you should return the SiteService.getAllSites().then() promise and then inside of that, return promises to chain so everything is linked (including proper error handling).

  3. Your .catch() handlers that just log are "eating" errors. If you .catch() and don't rethrow, it turns that promise into a resolved promise, thus "eating" the error and not propagating it.

Here's how you can fix those:

//FIND CHECKED OUT FILES
function getCheckedOutFiles() {
    console.log('Get checked out files');

    // Store final results and pass to then callback
    var checkedOutFiles = window.x = [];

    // Promise, returns collection with all sites 
    return SiteService.getAllSites().then(sites => {
            // For each site get lists then get items matching filter
            var promises = [];
            sites.forEach(function (site) {
                var web = new $pnp.Web(site.url);

                promises.push(web.lists
                    .filter("BaseTemplate eq 101")
                    .get() // Returns promise with all lists matching filter 
                    .then(lists => {
                        // Go through each list and find matching item 
                        var promises2 = [];
                        lists.forEach(function (list) {
                            promises2.push(web.lists.getByTitle(list.Title).items
                                .filter("CheckoutUser ne null")
                                .expand("File, File/Author, File/ModifiedBy, File/CheckedOutByUser")
                                .get() // Returns promise with all items matching filter 
                                .then(files => {
                                    // Loop through each item, get properties, add to collection 
                                    files.forEach(function (f) {
                                        var fileObject = {
                                            fileName: f.File.Name,
                                            fileUrl: f.File.ServerRelativeUrl,
                                            absoluteUrl: f.File.ServerRelativeUrl,
                                            checkedTo: f.File.CheckedOutByUser.Title,
                                            modified: f.Modified,
                                            modifiedBy: f.File.ModifiedBy.Title,
                                            createdBy: f.File.Author.Title,
                                            created: f.Created,
                                            version: f.File.UIVersionLabel
                                        };
                                        // Add file to collection 
                                        checkedOutFiles.push(fileObject);
                                    }, this);
                                })
                                .catch(e => {
                                    console.log(e);
                                    // propagate error
                                    throw e;
                                }));
                        });
                        return Promise.all(promises2);
                    }).catch(e => {
                        console.log(e);
                        // propagate error
                        throw e;
                    }));
            }, this);
            return Promise.all(promises).then(() => {
                // make checkedOutFiles by the final resolve value
                return checkedOutFiles;
            });
        });
};

// Returns promise with all checkedout file 
getCheckedOutFiles().then(files => {
    console.log("RESULTS", files);
}).catch(err => {
    // handle error here
});

This could be simplified somewhat by using Promise.map() in the Bluebird promise library which will iterate an array, calling a promise producing function and wait for all the promises to be done (a combination of .each() and Promise.all() ).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM