MKP MKP - 4 months ago 16
Javascript Question

Promises not resolved in a .each() loop

I've got to run a recursive process and the promises are not working as I want. This is the code:

var openAllLeves = function () {
openAll = 1;
$.when(openLevels()).then(openAll = 0);
}

var openLevels = function () {
var promises = [];
$('.myClass:not([data-type="T"])').each(function () {
var defer = $.Deferred();
$.when(loadLine($(this)).then(promises.push(defer)));
});
return $.when.apply(undefined, promises).promise();
}

var loadLine = function (thisObj) {
var defer = $.Deferred();
switch(nivel) {
case 1:
$.when(getPT($(thisObj).attr('data-a'))).then(defer.resolve());
break;
case 2:
// ...
}
return defer.promise();
}

var getPT = function (psn) {
var defer = $.Deferred();
var payload = { /* parameters... */ };
$.ajax({
url: webmethod,
data: payload,
type: "POST",
contentType: "application/json; charset=utf-8",
dataType: "json",
timeout: 10000,
success: function (data) {
$.when(paintPT(data)).then(function () {
if (openAll)
openLevels(), defer.resolve();
});
}
});
return defer.promise();
}


My problem is that openAll's value changes to 0 before being evaluated in the ajax function success code so only one iteration is performed and the recursivity is not done. It looks like .then is performed before resolving the array of promises.

The code is a little bit confusing so any help is appreciated.
Thanks in advance.

Answer

Avoid the deferred antipattern!

Also, when you pass something to .then(), it must be callback function, calling promises.push(defer), defer.resolve() and openAll = 0 or so does not work, it would execute that expression right away instead of waiting for the promise.

The $.when() and .promise() calls are mostly superfluous. Drop them.

function openAllLeves () {
    openAll = 1;
    openLevels().then(function() {
        openAll = 0
    });
}

function openLevels() {
    var promises = [];     
    $('.myClass:not([data-type="T"])').each(function () { // using `map` would be even better
        promises.push(loadLine($(this)));
    });
    return $.when.apply($, promises);
}

function loadLine(thisObj) {;
    switch(nivel) {
        case 1:
            return getPT($(thisObj).attr('data-a'))
        case 2:
            // ...
    }
}

function getPT(psn) {
    var payload = { /* parameters... */ };
    return $.ajax({
        url: webmethod,
        data: payload,
        type: "POST",
        contentType: "application/json; charset=utf-8",
        dataType: "json",
        timeout: 10000,
    }).then(function (data) {
        return paintPT(data);
    }).then(function () { 
        if (openAll) 
           openLevels();
    });
}

Btw, you will probably want to chain the if (openAll) openLevels(); to the return value of openLevels(), not to each single request promise.