Работа с массивами отложенных объектов

#jquery #jquery-deferred #deferred-execution #deferred-loading

#jquery #jquery-отложенный #отложенное выполнение #отложенная загрузка

Вопрос:

С момента использования $.Deferred я пару раз сталкивался с этим сценарием: у меня есть список значений, каждое из которых каким-то образом дает отложенный объект, и я хочу выполнить обратный вызов, как только все отложенные объекты будут разрешены.

Более конкретным примером может быть что-то вроде этого:

 var urls = [ 'foo.com', 'bar.com', 'baz.com', 'qux.com' ],
    defers = [], defer;

for( var i = 0, j = urls.length; i < j; i   ){
    defer = $.ajax({
        url: 'http://'   urls[ i ]
    });

    defers.push(defer);
}

$.when.apply(window, defers).done(function(){
    // Do Something
});
  

Есть ли более элегантное решение, чем код в моем примере?

Комментарии:

1. Почему вы думаете, что это не элегантно?

2. После написания подобного кода в 3-й раз я начал думать, что это довольно распространенный сценарий, и он может быть обработан платформой отложенных объектов лучшим способом, который я просто упускал из виду.

Ответ №1:

Да, вы никогда не должны ссылаться на значение поиска в цикле. Всегда делайте копию.

 var urls = [ 'foo.com', 'bar.com', 'baz.com', 'qux.com' ],
    defers = [], defer;

var urlsLength = urls.length;
for( var i = 0, j = urlsLength; i < j; i   ){
    defer = $.ajax({
        url: 'http://'   urls[ i ]
    });

    defers.push(defer);
}

$.when.apply(window, defers).done(function(){
    // Do Something
});
  

Но если серьезно, я просто издеваюсь над тобой. Этот код потрясает. Придерживайтесь этого.

Комментарии:

1. Вы правы в отношении значения поиска, но наличие его в объявлении (т.Е. var i = 0, j = urls.length ) кэширует его. Чего вы хотите избежать, так это наличия его в сравнении (т.е. i < urls.length ). 🙂

Ответ №2:

Более элегантный способ написания этого примера — использовать функцию отображения массива (или $.map от jQuery):

 var urls = [ 'foo.com', 'bar.com', 'baz.com', 'qux.com' ];

var defers = urls.map( function( url) {
    return $.ajax({
        url: 'http://'   url
    });
});

$.when.apply(window, defers).done(function(){
    // Do Something
});
  

Вы даже можете использовать свои собственные функции «whenDone» и «fetchURL»:

 Array.prototype.whenDone = function(callback){
    return $.when.apply(window, this).done(callback);
}

function fetchURL(url){
    return $.ajax({
        url: 'http://'   url
    });
}

var urls = [ 'foo.com', 'bar.com', 'baz.com', 'qux.com' ];    

urls.map( fetchUrl ).whenDone(function(){
    // Do Something
});
  

Ответ №3:

Вот вспомогательная функция, которую я написал, называется LoadInitialData, ее можно вызвать следующим образом LoadInitialData(urlArray, dataReturnedArray, callback)

     ///  
///  1. The magical function LoadInitialData
///  

            ///  
            /// <summary>
            ///    This functions allows you to fire off a bunch of ajax GET requests and run a callback function when
            ///    all the requests come back that contains an array of all your ajax success data
            /// </summary>
            /// <params>
            ///           urlArray - an array of urls to be looped and ajaxed
            ///    dataReturnedArray - this array will contain all data returned from your ajax calls. Its stuctured like this
            ///         [{url: "http//site.com/1", "data": "your data"}, {url: "http//site.com/2", "data": "your data"}]
            ///           dataReturnedArray[0] is data from call 1,  dataReturnedArray[1] is data from call 2 etc. It might be a 
            ///         good idea to pass in a global array so you can use this data throughout your application.
            ///     callback - a function that runs after all ajax calles are done, dataReturnedArray is available in the callback
            /// </parms>
            ///
            function LoadInitialData(urlArray, dataReturnedArray, callback){
               // set up a deffered promise to fire when all our async calls come back
                var urls = urlArray, defers = [], defer;
                   var urlsLength = urls.length;
                   for( var i = 0, j = urlsLength; i < j; i   ){
                       var u = urls[ i ];
                          defer = $.ajax({
                          type : "GET",
                           dataType : "jsonp",
                           url: u,
                           success: function(data){
                                 dataReturnedArray.push({
                                        url: u,
                                        data: data
                                 });
                           }
                       });
                       defers.push(defer);
                   }
                   $.when.apply(window, defers).then(function(){   
                          // Do Something now that we have all the data
                       console.log("done fetching all data");
                       callback(dataReturnedArray);
                   });
             }



///  
///  2. Your config…. urlArray, dataReturnedArray, callback
///  

         var app = app || {};
         app.data = []; // will hold the fetched data
         var urlArr = ["http://site.com/2", "http://site.com/2"]; // the urls to get data from


         // function to call once all the data is loaded
         callback = function(data){

            // data cleansing
            var tblData = [];                        
            $.each(data, function(key, value){
                   $.each(value.data, function(key, value){
                          tblData.push(value); 
                   });
             });

            $("#loader").hide();
         };


///  
///  3. Kick it all off!
///  

         // show a loader here
         $("#loader").show();

         // fire off the code to fetch the initial data
         LoadInitialData(urlArr, app.data, callback);