2

I am looping through an array, and in each loop, I add to an array of promises that is then passed into $q.all. Each chain includes a confirm() dialogue and a modal. The sequence of events for the user should be confirm() - modal - confirm() - modal. Instead, I'm getting confirm() - confirm() - modal - modal. Also, I'd like a function refreshSelection() to execute after the last modal is closed, but it currently fires as soon as the last confirm() dialogue is closed.

    var promiseArr = [];
    var selection = []; // [obj1, obj1, obj3...]

    for (var i = 0; i < selection.length; i++) {
        promiseArr.push(getData(i));
    }

    $q.all(promiseArr)
        .then(refreshSelection);

    function getData(i) {
        var opts = {}; // selection[i].id is param

        return $http(opts)
            .then(onGetSuccess(i));
    }

    function onGetSuccess(i) {
        return function(result) {
            var shouldOpenModal = confirm(selection[i].text);

            if (shouldOpenModal == true) {
                openModal();
            }
        }
    }

    function openModal() {
        var copyPunchesModal = $uibModal.open({
            // modal template options
            }
        });

        copyPunchesModal.result.then(otherFunc, function () {
            console.log("Modal closed");
        });
    }

    function refreshSelection() {
        selection = [];
    }

I have also tried the following to no avail.

    //...
    function getData(i) {
        var opts = {}; // selection[i].id is param

        return $http(opts)
            .then(onGetSuccess(i))
            .then(openConfirm)
            .then(openModal);
    }

Help is much appreciated.

devthorne
  • 197
  • 13

3 Answers3

2

Q.all doesn't specify the order in which the promises will be resolved. If you want the promises to be completed in the order of the array you need to chain the promises so that the current promise is executed inside the .then() of the previous promise.

You can do this cleanly with reduce.

I've got an answer here for ES6 promises, here it is adapted to Q:

deferred = $q.defer();
deferred.resolve();
return selection.reduce(
    (accumulator, current) => accumulator.then(()=>getData(current)), 
    deferred.promise
);

Note that you'll also need to modify your getData function to operate on the object itself (so currentSelection.text instead of selection[i].text, for example)

Community
  • 1
  • 1
Paarth
  • 9,687
  • 4
  • 27
  • 36
  • Thanks for the response. When `reduce()` moves to the second element in the array, accumulator is `undefined`. I removed the arrow functions, hopefully I didn't translate incorrectly. I'm also new to promises in general so not sure if the callbacks should be returning something different. https://jsfiddle.net/d0Lkp6q0/ – devthorne May 11 '16 at 17:38
  • That wasn't just an arrow function, that was an *expression-bodied arrow function*. As you might expect, these have only one expression and so don't need curly braces but they also **return** that expression. Have your construct function return the accumulator.then. What you return in a reduce is treated as the accumulated value for the next execution so if you're returning nothing the next value is undefined. – Paarth May 11 '16 at 19:04
  • Ah thanks for catching that. So now it seems that `reduce` is now iterating through the entire array, but `getData()` never executes. When/how should it get called? – devthorne May 11 '16 at 22:05
  • @devthorne can you update your question with your current code or post another jsfiddle/pastebin? – Paarth May 12 '16 at 03:37
  • https://jsfiddle.net/d0Lkp6q0/1/ - `getData` never fires. Also, I'm assuming `refreshSelection()` should be chained to `selection.reduce(...)` since it returns a promise? -- I tried chaining `.then()` to it, but that function doesn't execute either. Thanks again. – devthorne May 12 '16 at 15:23
  • Ah, of course, I'm sorry I overlooked that @devthorne I never fulfilled that deferred. Updated the answer. – Paarth May 12 '16 at 16:06
  • I had a feeling resolve() had to be called, though I'm a bit thrown off by the fact that it's called before the reduce() function constructs the promises. Do you have any recommended readings on promises? I found the Angular documentation on $q to be lacking for a beginner. I'm going to go ahead and mark this as the answer. There's still some unideal behavior -- I'm now getting confirm - modal - confirm - modal, but the second confirm covers the first modal. I think I can tinker from here, but let me know if you have additional tips. Thanks again! – devthorne May 12 '16 at 16:31
  • @devthorne try looking at the documentation for [the original Q library](https://github.com/kriskowal/q). Angular's $q doesn't have all of its features but it has the important ones and I think the original library does a better job of explaining some of its core functionality. – Paarth May 12 '16 at 16:37
  • @devthorne that kind of makes sense right now. If you want things to be queued in promises you have to wrap everything in a promise and return it to the calling function so it knows to insert it into the chain. The only thing the promise chain is aware of is your getData functions, if you want the other stuff to execute in order you're going to have to promisify openModal and onGetSuccess as well. – Paarth May 12 '16 at 16:43
0

You want to use a $.when function, which takes as an argument a number of promises and executes a function when they all complete. Alternatively, you can pipe them all together if you are ok with them completing in sequential order.

https://api.jquery.com/jquery.when/

user3538411
  • 338
  • 4
  • 15
0

I wanted to do something similar to avoid overflowing a device my program was controlling - basically I needed to wait for the async transaction to complete before starting the next one. I used promises to enforce both the order of execution, and to block until each item was complete.

// the async thing we want to do, wrapped in a promise
//
function sendMessage = (message) {
    var promise = new Promise (resolve, reject) {
        // send message via an async function that provides success & fail callbacks, e.g. AJAX
        // replace 'asyncFunction', it's just a placeholder in this example
        asyncFunction (message, 
            function (response) {   // success callback
                if (/*response is to my liking*/) {
                    resolve ();
                } else {
                    reject (new Error (/*why I didn't like the response*/));
                }
            },
            function (error) {  // failure callback
                reject (error);
            }
    };
    return promise;
}

// the array we want to process in order
var myMessages = ['hello', 'world', 'how', 'are', 'you', 'today?'];

// iterator, and recursive function to loop thru array
var idx = 0;
function next () {
    if (idx < myMessages.length) {
        sendMessage (myMessages[idx++]).then (
            next,   // maps to the 'resolve' call in sendMessage
            function (err) {    // maps to the 'reject' calls in sendMessage
                throw err;  // clearly, there's an option to be smarter than this
            }
        )
    }
}

// start the recursion
next ();
VorpalSword
  • 1,223
  • 2
  • 10
  • 25