Using jQuery.when with array of deferred objects c

2019-05-30 05:47发布

问题:

Let's say I have a site which saves phone numbers via an HTTP call to a service and the service returns the new id of the telephone number entry for binding to the telephone number on the page.

The telephones, in this case, are stored in an array called 'telephones' and datacontext.telephones.updateData sends the telephone to the server inside a $.Deferred([service call logic]).promise();

uploadTelephones = function (deffered) {
                for (var i = 0; i < telephones.length; i++){
                        deffered.push(datacontext.telephones.updateData(telephones[i], {
                            success: function (response) {
                                telephones[i].telephoneId = response;                                    
                            },
                            error: function () {
                                logger.error('Stuff errored');
                            }
                        }));                            
                 }
            }

Now if I call:

function(){
    var deferreds = [];
    uploadTelephones(deferreds);
    $.when.apply($, deferreds)
                    .then(function () {
                        editing(false);
                        complete();
                    },
                    function () {
                        complete();
                    });
}

A weird thing happens. All the telephones are sent back to the service and are saved. When the 'success' callback in uploadTelephones method is called with the new id as 'response', no matter which telephone the query relates to, the value of i is always telephones.length+1 and the line

telephones[i].telephoneId = response; 

throws an error because telephones[i] does not exist.

Can anyone tell me how to keep the individual values of i in the success callback?

回答1:

All of your closures (your anonymous functions capturing a variable in the local scope) refer to the same index variable, which will have the value of telephones.length after loop execution. What you need is to create a different variable for every pass through the for loop saving the value of i at the instance of creation at for later use.

To create a new different variable, the easiest way is to create an anonymous function with the code that is to capture the value at that particular place in the loop and immediately execute it.

either this:

for (var i = 0; i < telephones.length; i++)
{
    (function () {
        var saved = i;
        deffered.push(datacontext.telephones.updateData(telephones[saved],
        {
            success: function (response)
            {
                telephones[saved].telephoneId = response;
            },
            error: function ()
            {
                logger.error('Stuff errored ');
            }
        }));
    })();
}

or this:

for (var i = 0; i < telephones.length; i++)
{
    (function (saved) {
        deffered.push(datacontext.telephones.updateData(telephones[saved],
        {
            success: function (response)
            {
                telephones[saved].telephoneId = response;
            },
            error: function ()
            {
                logger.error('Stuff errored ');
            }
        }));
    })(i);
}  

should work.

Now, that's a bit ugly, though. Since you are already going through the process of executing an anonymous function over and over, if you want your code to be a little bit cleaner, you might want to look at Array.forEach and just use whatever arguments are passed in, or just use jQuery.each as you are already using jQuery.