2014-02-15 1 views
0

В моем JS мне нужно получить содержимое 3 файлов с помощью AJAX, а затем сделать некоторый код. Это привело к довольно странно выглядящему созданию вложенных асинхронных функций. Также в любое время, когда я работаю с асинхронными функциями, это уродливое гнездование появляется.jQuery, избавляющийся от вложенных функций ajax

Как я могу избежать функций вложенности, когда я просто хочу дождаться их завершения? (Я использую jQuery, если это помогает)

function loadFilesAndDoStuff() { 
    $.get(firstFile, function(first_file_data) { 
     $.get(secondFile, function(second_file_data) { 
     $.get(thirdFile, function(third_file_data) { 
      someOtherAsyncFunction(function(combined_file_data) { 
      // do some stuff with the "combined_file_data". 
      }); 
     }); 
     }); 
    }); 
    } 
+1

создать массив и рекурсивно вызвать функцию, пока вы дойдете до конца –

ответ

3

Вот несколько разных методов с использованием и без использования отложенных. Во всех случаях запускаются все вызовы ajax, после чего часть кода отслеживает, когда все вызовы ajax завершили и собирают данные из вызовов по мере их завершения, поэтому, когда последний завершает работу, все данные доступны.

Вы можете запустить все три Ajax звонки сразу и просто проверить в каждой функции завершения, если все они сделали еще, хранение результатов в локальной переменной, пока они не все сделали:

function loadFilesAndDoStuff() { 
    var cntr = 3; 
    var data1, data2, data3; 

    function checkDone() { 
     --cntr; 
     if (cntr === 0) { 
      // all three are done here 
      someOtherFunction(combined_file_data); 
     } 
    } 

    $.get(firstFile, function(data) { 
     data1 = data; 
     checkDone(); 
    }); 
    $.get(secondFile, function(data) { 
     data2 = data; 
     checkDone(); 
    }); 
    $.get(thirdFile, function(data) { 
     data3 = data; 
     checkDone(); 
    }); 
} 

Или, вы можете поместить больше его в общей функции и передать массив имен файлов функции:

function loadFilesAndDoStuff(filesArray) { 
    var results = []; 
    var doneCnt = 0; 

    function checkDone(index, data) { 
     results[index] = data; 
     ++doneCnt; 
     if (doneCnt === filesArray.length) { 
      // all results are in the results array now 
     } 
    } 

    for (var i = 0; i < filesArray.length; i++) { 
     results.push(null); 
     $.get(filesArray[i], checkDone.bind(this, i)); 
    } 
} 

Использование Deferreds, вы можете сделать это:

function loadFilesAndDoStuff(filesArray) { 
    var results = []; 
    var deferreds = []; 

    function doneOne(index, data) { 
     results[index] = data; 
    } 

    for (var i = 0; i < filesArray.length; i++) { 
     results.push(null); 
     deferreds.push($.get(filesArray[i], doneOne.bind(this, i))); 
    } 
    $.when.apply($, deferreds).done(function() { 
     // all ajax calls are done and results are available now 
    }); 
} 

Или еще короче версии, используя тот факт, что deferreds сохранить аргументы из обработчиков Sucess для каждого отсроченного:

function loadFilesAndDoStuff(filesArray) { 
    var deferreds = []; 

    for (var i = 0; i < filesArray.length; i++) { 
     deferreds.push($.get(filesArray[i])); 
    } 
    $.when.apply($, deferreds).done(function() { 
     // all ajax calls are done and results are available now 
     // arguments[0][0] is the data from the first $.get call 
     // arguments[1][0] is the data from the second $.get call 
     // and so on 
    }); 

} 

Работа демо из этого последнего варианта: http://jsfiddle.net/jfriend00/5ppU4/

FYI, нет никакой магии внутри $.when(). Если вы посмотрите на код jQuery для него, он просто сохранит счетчик, когда все переданные ему аргументы выполнены (как и в моих первых двух вариантах здесь). Основное различие заключается в том, что он использует интерфейс обещания для объекта jqXHR вместо того, чтобы знать, что это вызов ajax. Но концептуально он делает то же самое.


Вот еще один новый объект, который я написал для обработки нескольких отложенных.

function loadFilesAndDoStuff(filesArray) { 
    var deferred = $.MultiDeferred().done(function() { 
     // all ajax calls are done and results are available now 
     // arguments[0][0] is the data from the first $.get call 
     // arguments[1][0] is the data from the second $.get call 
     // and so on 
    }); 

    for (var i = 0; i < filesArray.length; i++) { 
     deferred.add($.get(filesArray[i])); 
    } 
} 

Код MultiDeferred является JQuery плагин написан специально для обработки с уведомлением, когда несколько deferreds сделаны и код для него здесь:

jQuery.MultiDeferred = function(/* zero or more promises */) { 

    // make the Deferred 
    var self = jQuery.Deferred(); 

    var remainingToFinish = 0; 
    var promises = []; 
    var args = []; 
    var anyFail = false; 
    var failImmediate = false; 

    function _add(p) { 
     // save our index in a local variable so it's available in this closure later 
     var index = promises.length; 

     // save this promise 
     promises.push(p); 
     // push placeholder in the args array 
     args.push([null]); 

     // one more waiting to finish 
     ++remainingToFinish; 

     // see if all the promises are done 
     function checkDone(fail) { 
      return function() { 
       anyFail |= fail; 
       // make copy of arguments so we can save them 
       args[index] = Array.prototype.slice.call(arguments, 0); 
       --remainingToFinish; 

       // send notification that one has finished 
       self.notify.apply(self, args[index]); 
       // if all promises are done, then resolve or reject 
       if (self.state() === "pending" && (remainingToFinish === 0 || (fail && failImmediate))){ 
        var method = anyFail ? "reject" : "resolve"; 
        self[method].apply(self, args); 
       } 
      } 
     } 
     // add our own monitors so we can collect all the data 
     // and keep track of whether any fail 
     p.done(checkDone(false)).fail(checkDone(true)); 
    } 

    // add a new promise 
    self.add = function(/* one or more promises or arrays of promises */) { 
     if (this.state() !== "pending") { 
      throw "Can't add to a deferred that is already resolved or rejected"; 
     } 
     for (var i = 0; i < arguments.length; i++) { 
      if (arguments[i] instanceof Array) { 
       for (var j = 0; j < arguments[i].length; j++) { 
        _add(arguments[i][j]); 
       } 
      } else { 
       _add(arguments[i]); 
      } 
     } 
     return this; 
    } 

    // get count of remaining promises that haven't completed yet 
    self.getRemaining = function() { 
     return remainingToFinish; 
    } 

    // get boolean on whether any promises have failed yet 
    self.getFailYet = function() { 
     return anyFail; 
    } 

    self.setFailImmediate = function(failQuick) { 
     failImmediate = failQuick; 
     return this; 
    } 

    // now process all the arguments 
    for (var i = 0; i < arguments.length; i++) { 
     self.add(arguments[i]); 
    } 
    return self;  
}; 
+0

Есть ли способ очистки? Вручную установить счет и рекурсивно пометить его, кажется, тоже странно. Может быть, что-то с отложенными объектами? Я тоже видел Арбитера.js, который может решить эту проблему без рекурсивной функции –

+0

Или, может быть, jQuery.Deferred() http://api.jquery.com/jQuery.Deferred/? –

+0

Я добавил еще одну версию с немного более распространенным кодом. – jfriend00

0

Создание массива каждого необходимого файла, затем прокрутите массив файлов и вызовите $.get на каждую итерацию и вызовите функцию объединения, которая объединит данные и проверит проверку счетчика, как только счет достигнет обратного вызова.

function loadData(files,callback){ 
    var combinedData = ""; 
    var count = 0; 
    function combineFile(data){ 
     count++; 
     combinedData += data; 
     if(count==files.length-1){ 
      callback(combinedData); 
     } 
    } 

    for(var i=0; i<files.length; i++){ 
     $.get(files[i],combineFile); 
    } 
} 

loadData(["files1.txt","files2.txt","files3.txt"],function(data){ 
    console.log("Combined Data: ",data); 
}); 
Смежные вопросы