跟踪各种 ajax post 请求的进度

Keeping track and progress of various ajax post requests

我正在使用 jQuery 发出各种 ajax POST 请求。我需要跟踪其中每一个的成功或失败,以及整个批次的总体进度,以便我可以使用进度条和有关成功请求数的信息更新 UI,以及失败的总数。

在尝试在我的应用程序中实现该功能之前,我一直在使用 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") });

全在这fiddle:http://jsfiddle.net/mmtbo7v6/1/

我需要的是能够提供回调,该回调应在所有 promises 都已解决(无论成功与否)后调用。

当上面的所有调用都设置为成功时(通过删除数组中第四个 fn 调用的 true 参数)它工作正常。输出打印以下内容:

done
done
done
done
done
done
all done
always
finished

但是即使单个调用设置为失败(因为它在 jsfiddle 中默认设置),输出如下:

done
FAIL
ALL FAIL
always
done
done
done
done

因此 none 的集体承诺回调(由 $.when 调用生成的回调)在所有承诺都得到解决后被调用。如果单个 ajax 调用失败,则根本不会调用最后的 .then

此外,我希望了解如何跟踪这批 ajax 调用的进度,以更新 UI 中的进度条。

嗯...我要不公平了。 jQuery 实际上与进度事件捆绑在一起,但我自己讨厌它们,因为我认为它们不能很好地组合或聚合 - 所以我将展示一个更简单的进度条替代方法,我 believe is superior 相反。

要事第一:

'all promises resolved but some possibly rejected' 问题通常称为 'settle'。我已经提供了一个类似问题的答案 here with just giving the results and 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);
]);

事实证明,这个问题有一个更好的替代方法,它可以替代 promises 方法。看看两种模式的组合:Observables + Iterables = Reactive programming.

Reactive Programming是用异步数据流编程,即将异步数据流当作集合来处理,可以像传统的集合数据类型一样进行遍历和转换。 This article 是一个很好的介绍。

我不会把这个答案post变成教程,所以我们直接上解决方案,如下所示。我要使用 RxJS library, but there are other libraries for Reactive Programming in 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 });
      });
  });
}

函数本身 returns 一个可观察对象,它是一个事件流。即,每个 promise 的事件都已完成,无论成功与否。我们可以使用返回的 observable 来监听这个流(或者订阅它,如果我们要遵守 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
});

就是这样。更简洁的代码,更函数式的编程方法。无需保持状态,一切都以更明确的方式表达。只是我们想要完成的而不是应该如何完成。