Search code examples
javascriptrestloopspromisenested-loops

Using Promises with Loops and Nested Functions


I am trying to use promises with loops and nested functions that are within some of those loops. I have a series of functions that are supposed to bring back SharePoint list items from REST calls - once those are done executing, then another function gets called that uses the data that was brought back.

Since there are multiple lists, and subsequently multiple list items in each, I used a while loop to make each REST call, and from there, the data (list items) are put into objects. Those objects gets placed into an array and that's what that second functions uses to continue on.

I'm having trouble receiving the responses from the promises. I was thinking to have multiple promises that get pushed to an array, then finally use Promise.all to see if everything resolved before using then. The problem is that all the promises stay pending since I'm not returning the resolve correctly. Please see below.

    function onQuerySuccess(sender, args) {
        var itemEnumerator = items.getEnumerator();

        while (itemEnumerator.moveNext()) {
            var promise = new Promise(function (resolve, reject) {
                var item = itemEnumerator.get_current();
                item = item.get_item('URL');
                var itemUrl = item.get_description();

                getRequestItemsFromList(itemUrl);
            });

            promises.push(promise); // all promises are present, but their status is pending
        }

        console.log(promises);

        Promise.all(promises).then(function (val) {
            console.log(val);
            execFuncs(); // function to execute once all the above are done
        }).catch(function (response) {
            console.log(response);
        });
    }

Because there's a lot of functions involved, so this is the order of execution:

getRequestItemsFromList //gets url for each list
execCrossDomainRequest (on success call) // makes REST call to get list and its items
cleanData // trims data and puts it in objects

The last is where I figured I call Promise.resolve() since that's the end of the line.

Either way, that's not working. I checked out other threads, but I'm trying to do this without using any libraries. Thanks in advance.

Edit:

Full relevant code:

var promises = [];

window.requests = [];

function getRequestLists() {
    var requestsLists = hostWeb.get_lists().getByTitle('Name');  // sharepoint list with all the request list urls.
    context.load(requestsLists);

    var camlQuery = new SP.CamlQuery();
    camlQuery.set_viewXml('<View></View>');
    var items = requestsLists.getItems(camlQuery);

    context.load(items, 'Include(URL)');

    context.executeQueryAsync(onQuerySuccess, onQueryFail);

    function onQuerySuccess(sender, args) {
        var itemEnumerator = items.getEnumerator();

        while (itemEnumerator.moveNext()) {
            var promise = new Promise(function (resolve, reject) {
                var item = itemEnumerator.get_current();
                item = item.get_item('URL');
                var itemUrl = item.get_description();

                getRequestItemsFromList(itemUrl);
            });

            promises.push(promise);
        }

        console.log(promises);

        Promise.all(promises).then(function (val) {
            console.log(val);
            execFuncs(); // not shown here
        }).catch(function (response) {
            console.log(response);
        });
    }

    function onQueryFail(sender, args) {
        alert("Request to retrieve list URL items has failed. " + args.get_message());
    }
}

function getRequestItemsFromList(url) {
    var lastPos = getSubstringIndex(url, "/", 4);
    var webUrl = url.substring(0, lastPos); // truncates list url to parse out web url       

    var absListPos = getSubstringIndex(url, "AllItems.aspx", 1);
    var absListUrl = url.substring(0, absListPos); // truncates the AllItems.aspx at the end of the list url

    var relListPos = getSubstringIndex(absListUrl, "/", 3);
    var relListUrl = absListUrl.substring(relListPos, absListUrl.length); // gets the list's relative url 

    var listName = "List Name";

    console.log(webUrl);
    execCrossDomainRequest(webUrl, listName, absListUrl);
}

function execCrossDomainRequest(webUrl, listName, absListUrl) {
    var executor = new SP.RequestExecutor(appWebUrl);

    executor.executeAsync({ // to collect the list description
        url: appWebUrl + "/_api/SP.AppContextSite(@target)/web/lists/getbytitle(@name)?" +
            "@target='" + webUrl + "'&@name='" + listName + "'" +
            "&$select=Description",
        method: "GET",
        headers: { "Accept": "application/json; odata=verbose" },
        success: onCallSuccess,
        error: onCallFail
    });

    function onCallSuccess(data) {
        var json = JSON.parse(data.body);
        var description = json.d.Description;

        executor.executeAsync({ // to collect the list item information
            url: appWebUrl + "/_api/SP.AppContextSite(@target)/web/lists/getbytitle(@name)/items?" +
                "@target='" + webUrl + "'&@name='" + listName + "'" +
                "&$top=500&$select=*," +
                "Assigned_x0020_To/Title" +
                "&$expand=Assigned_x0020_To/Id",
            method: "GET",
            headers: { "Accept": "application/json; odata=verbose" },
            success: onItemsCallSuccess,
            error: onItemsCallFail
        });

        function onItemsCallSuccess(data) {
            var itemsJson = JSON.parse(data.body);
            var results = itemsJson.d.results;

            cleanData(results, description, absListUrl);
        }

        function onItemsCallFail(data, errorCode, errorMessage) {
            console.log("Could not make list items cross domain call. " + errorMessage);
        }
    }

    function onCallFail(data, errorCode, errorMessage) {
        console.log("Could not make list cross domain call. " + errorMessage);
    }
}

function cleanData(results, listDescription, absListUrl) {

    if (!results.length) {
        return;
    }

    for (var i = 0; i < results.length; i++) {
        var client = listDescription;
        var id = results[i].ID;
        ...

        }

        var request = new Request(client, id, path, title, status, estimated, assignedTo, priority, description, response);

        window.requests.push(request);
    }

    return Promise.resolve();
}

Solution

  • When you use a promise constructor like this:

    var promise = new Promise(function (resolve, reject) {
    

    It implies that somewhere inside this block you are calling resolve and/or reject, to settle the promise.

    But you are never doing it, leaving the created promise object in pending state forever.

    See Constructing a promise.