2015-01-21 3 views
1

Я использую jQuery для создания различных запросов POST ajax. Мне нужно отслеживать успех или неудачу каждого из них, а также общий прогресс всей партии, чтобы я мог обновить пользовательский интерфейс с индикатором выполнения и информацией о том, сколько запросов удалось, и сколько из них не удалось, из общей суммы.Отслеживание и прогресс различных запросов ajax post

Прежде чем пытаться реализовать функцию в своем приложении, я играл с некоторым кодом в jsfiddle в качестве доказательства концепции, пока не повезло. Это то, что я получил:

// an alternative to console.log to see the log in the web page 
var fnLog = function(message) { 
    $('#console').append($("<p>" + message + "</p>")); 
}; 

// keeping track of how many ajax calls have been finished (successfully or not) 
var count = 0; 

// a dummy ajax call that succeeds by default 
var fn = function(shouldFail) { 
    return $.get(shouldFail ? '/echo/fail/' : '/echo/json/') 
     .done(function() { fnLog("done") }) 
     .fail(function() { fnLog("FAIL") }); 
}; 

// a set of different asynchronous ajax calls 
var calls = [fn(),fn(),fn(),fn(true),fn(),fn()]; 

// an attempt to make a collective promise out of all the calls above 
$.when.apply($, calls) 
    .done(function() { fnLog("all done") }) 
    .fail(function() { fnLog("ALL FAIL") }) 
    .always(function() { fnLog("always") }) 
    .progress(function(arg) { fnLog("progress" + arg) }) 
    .then(function() { fnLog("finished") }); 

Это все в этой скрипке: http://jsfiddle.net/mmtbo7v6/1/

Что мне нужно, это способность обеспечить обратный вызов, который должен быть вызван после того, как все обещания будут решены (или успешно или не).

Когда все вышеперечисленные вызовы установлены на успешное выполнение (путем удаления аргумента true четвертого вызова fn в массиве), он отлично работает. Выходные печатает следующее:

done 
done 
done 
done 
done 
done 
all done 
always 
finished 

Но когда даже один вызов устанавливается на провал (как по умолчанию в jsfiddle), выход заключается в следующем:

done 
FAIL 
ALL FAIL 
always 
done 
done 
done 
done 

Так ни один из коллективные обещания обратного вызова (тот, который генерируется вызовом $.when) вызывается после того, как все обещания разрешены. Окончательный .then не вызывается вообще, если один вызов ajax терпит неудачу.

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

ответ

3

Ну ... Я собираюсь быть несправедливым. jQuery на самом деле поставляется в комплекте с событиями прогрессирования, но я сам их ненавижу, потому что я не думаю, что они сочиняют или объединяются хорошо, поэтому я покажу более простой альтернативный подход для этого индикатора выполнения, который вместо этого я использую believe is superior.

Первая вещь первая:

«все обещания решить, но некоторые, возможно, отклонил» вопрос называется «оседает» обычно. Я дал ответ на аналогичный вопрос here, просто предоставив результаты и here, обеспечивающий реализацию, которая дает вам доступ ко всем результатам, даже к отклоняемым.

function settle(promises){ 
    var d = $.Deferred(); 
    var counter = 0; 
    var results = Array(promises.length); 
    promises.forEach(function(p,i){ 
     p.then(function(v){ // add as fulfilled 
       results[i] = {state:"fulfilled", promise : p, value: v}; 
     }).catch(function(r){ // add as rejected 
       results[i] = {state:"rejected", promise : p, reason: r}; 
     }).always(function(){ // when any promises resolved or failed 
      counter++; // notify the counter 
      if (counter === promises.length) { 
       d.resolve(results); // resolve the deferred. 
      } 
     }); 
    }); 
    return d.promise(); 
} 

Вы бы использовать settle вместо $.when, чтобы получить желаемые результаты.

Что касается прогрессирования - я лично рекомендую передать обратный вызов прогрессии самому методу. Картина выходит что-то вроде этого:

function settle(promises, progress){ 
    progress = progress || function(){}; // in case omitted 
    var d = $.Deferred(); 
    var counter = 0; 
    var results = Array(promises.length); 
    promises.forEach(function(p,i){ 
     p.then(function(v){ // add as fulfilled 
       results[i] = {state:"fulfilled", promise : p, value: v}; 
     }).catch(function(r){ // add as rejected 
       results[i] = {state:"rejected", promise : p, reason: r}; 
     }).always(function(){ // when any promises resolved or failed 
      counter++; // notify the counter 
      progress((promises.length - counter)/promises.length); 
      if (counter === promises.length) { 
       d.resolve(results); // resolve the deferred. 
      } 
     }); 
    }); 
    return d.promise(); 
} 

Что позволит вам сделать что-то вроде:

settle([url1, url2, ... url100].map($.get), function(soFar){ 
    $("#myProgressBar").css("width", (soFar * 100)+"%"); 
}).then(function(results){ 
    console.log("All settled", results); 
]); 
+0

Большое спасибо @Benjamin. Я уже подозревал, что неотъемлемый способ выполнения обещаний не будет служить моей цели, но я не нашел способ его решить. Ваш ответ не только решает мою проблему, но и дает мне больше информации о том, как использовать '' .Deferred' для jQuery для моих собственных целей и для достижения пользовательского поведения, такого как тот, который мне нужен в этом конкретном случае. Еще раз спасибо. – Ernesto

+0

Эй, @ Benjamin, я реализую часть прогресса этого, и я выкопал немного больше о способе выполнения jQuery, который, как вы говорите, считается уступающим. Мне интересно, почему, потому что на самом деле довольно просто интегрировать в код, который вы показываете выше, без необходимости передавать второй аргумент функции 'sett'. Затем, увеличив счетчик '' '' 'call', вы можете вызвать' d.notify (counter, promises.length) 'вместо своего собственного обратного вызова' progress'. Наконец, вы можете зарегистрировать обратные вызовы прогресса к возвращенному обещанию, и они будут вызваны всякий раз, когда вызывается 'd.notify'. – Ernesto

1

Оказывается, есть гораздо лучшая альтернатива к этой проблеме, один из которых Shadows подход обещает. Сочетание двух шаблонов: Observables + Iterables = Реактивное программирование.

Реактивное программирование - это программирование с асинхронными потоками данных, то есть обработка асинхронных потоков данных в виде коллекций, которые можно перемещать и преобразовывать в традиционные типы данных коллекции. This article - отличное введение.

Я не буду преобразовывать этот ответ в учебник, поэтому давайте перейдем к решению, которое показано ниже. Я собираюсь использовать библиотеку RxJS, но есть и другие библиотеки для реактивного программирования в JS (bacon.js, похоже, действительно популярны).

function settle(promises) { 
    return Rx.Observable.from(promises).concatMap(function(promise, index) { 
    return Rx.Observable.fromPromise(promise). 
     map(function(response) { 
     return { count: index+1, total: promises.length, state: "fulfilled", promise: promise, value: response }; 
     }). 
     catch(function(reason) { 
     return Rx.Observable.of({ count: index+1, total: promises.length, state: "rejected", promise: promise, reason: reason }); 
     }); 
    }); 
} 

Функция сама возвращает наблюдаемую, которая является потоком событий. А именно, события каждого обещания закончились, либо успешно, либо нет. Мы можем использовать возвращаемые наблюдаемые для прослушивания этого потока (или подписаться на него, если мы придерживаемся терминологии RxJS).

var results = settle(promises); 

results.subscribeOnNext(function(results) { 
    // process each result as it arrives 
    // progress info can be extracted from results.count and results.total 
}); 

results.subscribeOnCompleted(function() { 
    // completion callback 
}); 

И все. Более чистый код, более функциональный подход к программированию. Не нужно сохранять состояние, все выражено более декларативным образом. Только какой мы хотим сделать, нет, как это должно быть сделано.

Смежные вопросы